Separate modules to provide java6/jodatime support and java8/java-time support
This commit is contained in:
parent
a0ef5be7b1
commit
2f7c18e190
18
README.md
18
README.md
|
@ -4,10 +4,26 @@ cron
|
|||
Cron expression parser and evaluator.
|
||||
|
||||
Allows for specifying cron - expressions (in Unix or Quartz like format) and evaluating when it will next match.
|
||||
|
||||
|
||||
|
||||
2016-09-11: rewritten to Java 8 DateTime by zemiak
|
||||
|
||||
usage
|
||||
=====
|
||||
See javadoc
|
||||
|
||||
|
||||
Change history
|
||||
==============
|
||||
|
||||
version 1.4:
|
||||
2017-02-13: added support for java6 (supports android 4) by adelnizamutdinov
|
||||
2016-09-11: rewritten to Java 8 DateTime by zemiak
|
||||
|
||||
version 1.3:
|
||||
2015-09-23: added timezone to tests by alf
|
||||
|
||||
version 1.2:
|
||||
2015-08-05: added protection for endless loop when looking up Feb 30th & optional seconds by michaelknigge
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package fc.cron;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2012 Frode Carlsen.
|
||||
* Copyright (C) 2012- Frode Carlsen.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -25,6 +26,9 @@ import java.util.regex.Matcher;
|
|||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* This provides cron support for java8 using java-time.
|
||||
* <P>
|
||||
*
|
||||
* Parser for unix-like cron expressions: Cron expressions allow specifying combinations of criteria for time
|
||||
* such as: "Each Monday-Friday at 08:00" or "Every last friday of the month at 01:30"
|
||||
* <p>
|
||||
|
@ -86,7 +90,7 @@ import java.util.regex.Pattern;
|
|||
* <P>
|
||||
* '*' Can be used in all fields and means 'for all values'. E.g. "*" in minutes, means 'for all minutes'
|
||||
* <P>
|
||||
* '?' Ca be used in Day-of-month and Day-of-week fields. Used to signify 'no special value'. It is used when one want
|
||||
* '?' Can be used in Day-of-month and Day-of-week fields. Used to signify 'no special value'. It is used when one want
|
||||
* to specify something for one of those two fields, but not the other.
|
||||
* <P>
|
||||
* '-' Used to specify a time interval. E.g. "10-12" in Hours field means 'for hours 10, 11 and 12'
|
||||
|
@ -114,22 +118,19 @@ import java.util.regex.Pattern;
|
|||
* - the third). If the day does not exist (e.g. "5#5" - 5th friday of month) and there aren't 5 fridays in
|
||||
* the month, then it won't match until the next month with 5 fridays.
|
||||
* <P>
|
||||
* <b>Case-sensitivt</b> No fields are case-sensitive
|
||||
* <b>Case-sensitive</b> No fields are case-sensitive
|
||||
* <P>
|
||||
* <b>Dependencies between fields</b> Fields are always evaluated independently, but the expression doesn't match until
|
||||
* the constraints of each field are met.Feltene evalueres Overlap of intervals are not allowed. That is: for
|
||||
* the constraints of each field are met. Overlap of intervals are not allowed. That is: for
|
||||
* Day-of-week field "FRI-MON" is invalid,but "FRI-SUN,MON" is valid
|
||||
*
|
||||
*/
|
||||
public class CronExpression {
|
||||
|
||||
enum CronFieldType {
|
||||
SECOND(0, 59, null),
|
||||
MINUTE(0, 59, null),
|
||||
HOUR(0, 23, null),
|
||||
DAY_OF_MONTH(1, 31, null),
|
||||
MONTH(1, 12, Arrays.asList("JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC")),
|
||||
DAY_OF_WEEK(1, 7, Arrays.asList("MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"));
|
||||
SECOND(0, 59, null), MINUTE(0, 59, null), HOUR(0, 23, null), DAY_OF_MONTH(1, 31, null), MONTH(1, 12,
|
||||
Arrays.asList("JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC")), DAY_OF_WEEK(1, 7,
|
||||
Arrays.asList("MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"));
|
||||
|
||||
final int from, to;
|
||||
final List<String> names;
|
||||
|
@ -163,8 +164,7 @@ public class CronExpression {
|
|||
final int expectedParts = withSeconds ? 6 : 5;
|
||||
final String[] parts = expr.split("\\s+"); //$NON-NLS-1$
|
||||
if (parts.length != expectedParts) {
|
||||
throw new IllegalArgumentException(String.format("Invalid cron expression [%s], expected %s felt, got %s"
|
||||
, expr, expectedParts, parts.length));
|
||||
throw new IllegalArgumentException(String.format("Invalid cron expression [%s], expected %s field, got %s", expr, expectedParts, parts.length));
|
||||
}
|
||||
|
||||
int ix = withSeconds ? 1 : 0;
|
||||
|
@ -199,7 +199,8 @@ public class CronExpression {
|
|||
}
|
||||
|
||||
public ZonedDateTime nextTimeAfter(ZonedDateTime afterTime, ZonedDateTime dateTimeBarrier) {
|
||||
ZonedDateTime nextTime = ZonedDateTime.from(afterTime).withNano(0).plusSeconds(1).withNano(0);;
|
||||
ZonedDateTime nextTime = ZonedDateTime.from(afterTime).withNano(0).plusSeconds(1).withNano(0);
|
||||
;
|
||||
|
||||
while (true) { // day of week
|
||||
while (true) { // month
|
||||
|
@ -261,17 +262,17 @@ public class CronExpression {
|
|||
}
|
||||
|
||||
abstract static class BasicField {
|
||||
private static final Pattern CRON_FELT_REGEXP = Pattern
|
||||
private static final Pattern CRON_FIELD_REGEXP = Pattern
|
||||
.compile("(?: # start of group 1\n"
|
||||
+ " (?:(\\*)|(\\?)|(L)) # globalt flag (L, ?, *)\n"
|
||||
+ " | ([0-9]{1,2}|[a-z]{3,3}) # or start number or symbol\n"
|
||||
+ " (?:(?<all>\\*)|(?<ignore>\\?)|(?<last>L)) # global flag (L, ?, *)\n"
|
||||
+ " | (?<start>[0-9]{1,2}|[a-z]{3,3}) # or start number or symbol\n"
|
||||
+ " (?: # start of group 2\n"
|
||||
+ " (L|W) # modifier (L,W)\n"
|
||||
+ " | -([0-9]{1,2}|[a-z]{3,3}) # or end nummer or symbol (in range)\n"
|
||||
+ " (?<mod>L|W) # modifier (L,W)\n"
|
||||
+ " | -(?<end>[0-9]{1,2}|[a-z]{3,3}) # or end nummer or symbol (in range)\n"
|
||||
+ " )? # end of group 2\n"
|
||||
+ ") # end of group 1\n"
|
||||
+ "(?:(/|\\#)([0-9]{1,7}))? # increment and increment modifier (/ or \\#)\n"
|
||||
, Pattern.CASE_INSENSITIVE | Pattern.COMMENTS);
|
||||
+ "(?:(?<incmod>/|\\#)(?<inc>[0-9]{1,7}))? # increment and increment modifier (/ or \\#)\n",
|
||||
Pattern.CASE_INSENSITIVE | Pattern.COMMENTS);
|
||||
|
||||
final CronFieldType fieldType;
|
||||
final List<FieldPart> parts = new ArrayList<>();
|
||||
|
@ -284,15 +285,15 @@ public class CronExpression {
|
|||
private void parse(String fieldExpr) { // NOSONAR
|
||||
String[] rangeParts = fieldExpr.split(",");
|
||||
for (String rangePart : rangeParts) {
|
||||
Matcher m = CRON_FELT_REGEXP.matcher(rangePart);
|
||||
Matcher m = CRON_FIELD_REGEXP.matcher(rangePart);
|
||||
if (!m.matches()) {
|
||||
throw new IllegalArgumentException("Invalid cron field '" + rangePart + "' for field [" + fieldType + "]");
|
||||
}
|
||||
String startNummer = m.group(4);
|
||||
String modifier = m.group(5);
|
||||
String sluttNummer = m.group(6);
|
||||
String inkrementModifier = m.group(7);
|
||||
String inkrement = m.group(8);
|
||||
String startNummer = m.group("start");
|
||||
String modifier = m.group("mod");
|
||||
String sluttNummer = m.group("end");
|
||||
String incrementModifier = m.group("incmod");
|
||||
String increment = m.group("inc");
|
||||
|
||||
FieldPart part = new FieldPart();
|
||||
part.increment = 999;
|
||||
|
@ -302,26 +303,26 @@ public class CronExpression {
|
|||
if (sluttNummer != null) {
|
||||
part.to = mapValue(sluttNummer);
|
||||
part.increment = 1;
|
||||
} else if (inkrement != null) {
|
||||
} else if (increment != null) {
|
||||
part.to = fieldType.to;
|
||||
} else {
|
||||
part.to = part.from;
|
||||
}
|
||||
} else if (m.group(1) != null) {
|
||||
} else if (m.group("all") != null) {
|
||||
part.from = fieldType.from;
|
||||
part.to = fieldType.to;
|
||||
part.increment = 1;
|
||||
} else if (m.group(2) != null) {
|
||||
part.modifier = m.group(2);
|
||||
} else if (m.group(3) != null) {
|
||||
part.modifier = m.group(3);
|
||||
} else if (m.group("ignore") != null) {
|
||||
part.modifier = m.group("ignore");
|
||||
} else if (m.group("last") != null) {
|
||||
part.modifier = m.group("last");
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid cron part: " + rangePart);
|
||||
}
|
||||
|
||||
if (inkrement != null) {
|
||||
part.incrementModifier = inkrementModifier;
|
||||
part.increment = Integer.valueOf(inkrement);
|
||||
if (increment != null) {
|
||||
part.incrementModifier = incrementModifier;
|
||||
part.increment = Integer.valueOf(increment);
|
||||
}
|
||||
|
||||
validateRange(part);
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Frode Carlsen
|
||||
* Copyright (C) 2012- Frode Carlsen
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -15,10 +15,10 @@
|
|||
*/
|
||||
package fc.cron;
|
||||
|
||||
import fc.cron.CronExpression.CronFieldType;
|
||||
import fc.cron.CronExpression.DayOfMonthField;
|
||||
import fc.cron.CronExpression.DayOfWeekField;
|
||||
import fc.cron.CronExpression.SimpleField;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.YearMonth;
|
||||
import java.time.ZoneId;
|
||||
|
@ -27,11 +27,16 @@ import java.util.Arrays;
|
|||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.junit.After;
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import fc.cron.CronExpression.CronFieldType;
|
||||
import fc.cron.CronExpression.DayOfMonthField;
|
||||
import fc.cron.CronExpression.DayOfWeekField;
|
||||
import fc.cron.CronExpression.SimpleField;
|
||||
|
||||
public class CronExpressionTest {
|
||||
TimeZone original;
|
||||
ZoneId zoneId;
|
|
@ -16,11 +16,17 @@
|
|||
<name>cron-jodatime</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<dependency>
|
||||
<groupId>joda-time</groupId>
|
||||
<artifactId>joda-time</artifactId>
|
||||
<version>2.9.7</version>
|
||||
</dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>3.6.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
|
|
@ -29,12 +29,15 @@ import org.joda.time.LocalDate;
|
|||
import org.joda.time.MutableDateTime;
|
||||
|
||||
/**
|
||||
* This provides cron support for java6 upwards and jodatime.
|
||||
* <P>
|
||||
*
|
||||
* Parser for unix-like cron expressions: Cron expressions allow specifying combinations of criteria for time
|
||||
* such as: "Each Monday-Friday at 08:00" or "Every last friday of the month at 01:30"
|
||||
* <p>
|
||||
* A cron expressions consists of 5 or 6 mandatory fields (seconds may be omitted) separated by space. <br>
|
||||
* These are:
|
||||
*
|
||||
*
|
||||
* <table cellspacing="8">
|
||||
* <tr>
|
||||
* <th align="left">Field</th>
|
||||
|
@ -86,11 +89,11 @@ import org.joda.time.MutableDateTime;
|
|||
* <td align="left"><code>, - * ? / L #</code></td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
*
|
||||
* <P>
|
||||
* '*' Can be used in all fields and means 'for all values'. E.g. "*" in minutes, means 'for all minutes'
|
||||
* <P>
|
||||
* '?' Ca be used in Day-of-month and Day-of-week fields. Used to signify 'no special value'. It is used when one want
|
||||
* '?' Can be used in Day-of-month and Day-of-week fields. Used to signify 'no special value'. It is used when one want
|
||||
* to specify something for one of those two fields, but not the other.
|
||||
* <P>
|
||||
* '-' Used to specify a time interval. E.g. "10-12" in Hours field means 'for hours 10, 11 and 12'
|
||||
|
@ -118,12 +121,12 @@ import org.joda.time.MutableDateTime;
|
|||
* - the third). If the day does not exist (e.g. "5#5" - 5th friday of month) and there aren't 5 fridays in
|
||||
* the month, then it won't match until the next month with 5 fridays.
|
||||
* <P>
|
||||
* <b>Case-sensitivt</b> No fields are case-sensitive
|
||||
* <b>Case-sensitive</b> No fields are case-sensitive
|
||||
* <P>
|
||||
* <b>Dependencies between fields</b> Fields are always evaluated independently, but the expression doesn't match until
|
||||
* the constraints of each field are met.Feltene evalueres Overlap of intervals are not allowed. That is: for
|
||||
* the constraints of each field are met. Overlap of intervals are not allowed. That is: for
|
||||
* Day-of-week field "FRI-MON" is invalid,but "FRI-SUN,MON" is valid
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class CronExpression {
|
||||
|
||||
|
@ -274,13 +277,13 @@ public class CronExpression {
|
|||
}
|
||||
|
||||
abstract static class BasicField {
|
||||
private static final Pattern CRON_FELT_REGEXP = Pattern
|
||||
private static final Pattern CRON_FIELD_REGEXP = Pattern
|
||||
.compile("(?: # start of group 1\n"
|
||||
+ " (?:(\\*)|(\\?)|(L)) # globalt flag (L, ?, *)\n"
|
||||
+ " (?:(\\*)|(\\?)|(L)) # global flag (L, ?, *)\n"
|
||||
+ " | ([0-9]{1,2}|[a-z]{3,3}) # or start number or symbol\n"
|
||||
+ " (?: # start of group 2\n"
|
||||
+ " (L|W) # modifier (L,W)\n"
|
||||
+ " | -([0-9]{1,2}|[a-z]{3,3}) # or end nummer or symbol (in range)\n"
|
||||
+ " | -([0-9]{1,2}|[a-z]{3,3}) # or end number or symbol (in range)\n"
|
||||
+ " )? # end of group 2\n"
|
||||
+ ") # end of group 1\n"
|
||||
+ "(?:(/|\\#)([0-9]{1,7}))? # increment and increment modifier (/ or \\#)\n"
|
||||
|
@ -297,25 +300,25 @@ public class CronExpression {
|
|||
private void parse(String fieldExpr) { // NOSONAR
|
||||
String[] rangeParts = fieldExpr.split(",");
|
||||
for (String rangePart : rangeParts) {
|
||||
Matcher m = CRON_FELT_REGEXP.matcher(rangePart);
|
||||
Matcher m = CRON_FIELD_REGEXP.matcher(rangePart);
|
||||
if (!m.matches()) {
|
||||
throw new IllegalArgumentException("Invalid cron field '" + rangePart + "' for field [" + fieldType + "]");
|
||||
}
|
||||
String startNummer = m.group(4);
|
||||
String startNumber = m.group(4);
|
||||
String modifier = m.group(5);
|
||||
String sluttNummer = m.group(6);
|
||||
String inkrementModifier = m.group(7);
|
||||
String inkrement = m.group(8);
|
||||
String endNumber = m.group(6);
|
||||
String incrementModifier = m.group(7);
|
||||
String increment = m.group(8);
|
||||
|
||||
FieldPart part = new FieldPart();
|
||||
part.increment = 999;
|
||||
if (startNummer != null) {
|
||||
part.from = mapValue(startNummer);
|
||||
if (startNumber != null) {
|
||||
part.from = mapValue(startNumber);
|
||||
part.modifier = modifier;
|
||||
if (sluttNummer != null) {
|
||||
part.to = mapValue(sluttNummer);
|
||||
if (endNumber != null) {
|
||||
part.to = mapValue(endNumber);
|
||||
part.increment = 1;
|
||||
} else if (inkrement != null) {
|
||||
} else if (increment != null) {
|
||||
part.to = fieldType.to;
|
||||
} else {
|
||||
part.to = part.from;
|
||||
|
@ -332,9 +335,9 @@ public class CronExpression {
|
|||
throw new IllegalArgumentException("Invalid cron part: " + rangePart);
|
||||
}
|
||||
|
||||
if (inkrement != null) {
|
||||
part.incrementModifier = inkrementModifier;
|
||||
part.increment = Integer.valueOf(inkrement);
|
||||
if (increment != null) {
|
||||
part.incrementModifier = incrementModifier;
|
||||
part.increment = Integer.valueOf(increment);
|
||||
}
|
||||
|
||||
validateRange(part);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Frode Carlsen
|
||||
* Copyright (C) 2012- Frode Carlsen
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package fc.cron;
|
||||
|
||||
import static org.fest.assertions.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
|
|
40
pom.xml
40
pom.xml
|
@ -1,10 +1,11 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>fc.cron</groupId>
|
||||
<artifactId>cron</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>1.3</version>
|
||||
<packaging>pom</packaging>
|
||||
<version>1.4-SNAPSHOT</version>
|
||||
<name>cron</name>
|
||||
<url>https://github.com/frode-carlsen/cron</url>
|
||||
|
||||
|
@ -15,43 +16,20 @@
|
|||
</license>
|
||||
</licenses>
|
||||
|
||||
<modules>
|
||||
<module>java8</module>
|
||||
<module>jodatime</module>
|
||||
</modules>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>joda-time</groupId>
|
||||
<artifactId>joda-time</artifactId>
|
||||
<version>2.3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.easytesting</groupId>
|
||||
<artifactId>fest-assert</artifactId>
|
||||
<version>1.4</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.11</version>
|
||||
<version>4.12</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.1</version>
|
||||
<configuration>
|
||||
<fork>true</fork>
|
||||
<source>1.7</source>
|
||||
<target>1.7</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
|
|
Loading…
Reference in New Issue