Separate modules to provide java6/jodatime support and java8/java-time support

This commit is contained in:
Frode Carlsen 2017-03-01 19:03:56 +01:00
parent a0ef5be7b1
commit 2f7c18e190
7 changed files with 108 additions and 99 deletions

View File

@ -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

View File

@ -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: &quot;Each Monday-Friday at 08:00&quot; or &quot;Every last friday of the month at 01:30&quot;
* <p>
@ -86,7 +90,7 @@ import java.util.regex.Pattern;
* <P>
* '*' Can be used in all fields and means 'for all values'. E.g. &quot;*&quot; 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. &quot;10-12&quot; 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. &quot;5#5&quot; - 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 &quot;FRI-MON&quot; is invalid,but &quot;FRI-SUN,MON&quot; 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);

View File

@ -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;

View File

@ -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>

View File

@ -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: &quot;Each Monday-Friday at 08:00&quot; or &quot;Every last friday of the month at 01:30&quot;
* <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. &quot;*&quot; 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. &quot;10-12&quot; 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. &quot;5#5&quot; - 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 &quot;FRI-MON&quot; is invalid,but &quot;FRI-SUN,MON&quot; 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);

View File

@ -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
View File

@ -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>