diff --git a/README.md b/README.md index a9fa49c..9ab7826 100644 --- a/README.md +++ b/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 + + diff --git a/src/main/java/fc/cron/CronExpression.java b/java8/src/main/java/fc/cron/CronExpression.java similarity index 88% rename from src/main/java/fc/cron/CronExpression.java rename to java8/src/main/java/fc/cron/CronExpression.java index a8baef1..74e7698 100644 --- a/src/main/java/fc/cron/CronExpression.java +++ b/java8/src/main/java/fc/cron/CronExpression.java @@ -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. + *

+ * * 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" *

@@ -86,7 +90,7 @@ import java.util.regex.Pattern; *

* '*' Can be used in all fields and means 'for all values'. E.g. "*" in minutes, means 'for all minutes' *

- * '?' 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. *

* '-' 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. *

- * Case-sensitivt No fields are case-sensitive + * Case-sensitive No fields are case-sensitive *

* Dependencies between fields 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 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" + + " (?:(?\\*)|(?\\?)|(?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" + + " (?L|W) # modifier (L,W)\n" + + " | -(?[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); + + "(?:(?/|\\#)(?[0-9]{1,7}))? # increment and increment modifier (/ or \\#)\n", + Pattern.CASE_INSENSITIVE | Pattern.COMMENTS); final CronFieldType fieldType; final List 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); diff --git a/src/test/java/fc/cron/CronExpressionTest.java b/java8/src/test/java/fc/cron/CronExpressionTest.java similarity index 99% rename from src/test/java/fc/cron/CronExpressionTest.java rename to java8/src/test/java/fc/cron/CronExpressionTest.java index 06633b2..aba3946 100644 --- a/src/test/java/fc/cron/CronExpressionTest.java +++ b/java8/src/test/java/fc/cron/CronExpressionTest.java @@ -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; diff --git a/jodatime/pom.xml b/jodatime/pom.xml index 30fcdda..661f596 100644 --- a/jodatime/pom.xml +++ b/jodatime/pom.xml @@ -16,11 +16,17 @@ cron-jodatime - + joda-time joda-time 2.9.7 - + + + org.assertj + assertj-core + 3.6.2 + test + diff --git a/jodatime/src/main/java/fc/cron/CronExpression.java b/jodatime/src/main/java/fc/cron/CronExpression.java index 2acb375..a27c015 100644 --- a/jodatime/src/main/java/fc/cron/CronExpression.java +++ b/jodatime/src/main/java/fc/cron/CronExpression.java @@ -29,12 +29,15 @@ import org.joda.time.LocalDate; import org.joda.time.MutableDateTime; /** + * This provides cron support for java6 upwards and jodatime. + *

+ * * 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" *

* A cron expressions consists of 5 or 6 mandatory fields (seconds may be omitted) separated by space.
* These are: - * + * * * * @@ -86,11 +89,11 @@ import org.joda.time.MutableDateTime; * * *
Field, - * ? / L #
- * + * *

* '*' Can be used in all fields and means 'for all values'. E.g. "*" in minutes, means 'for all minutes' *

- * '?' 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. *

* '-' 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. *

- * Case-sensitivt No fields are case-sensitive + * Case-sensitive No fields are case-sensitive *

* Dependencies between fields 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); diff --git a/jodatime/src/test/java/fc/cron/CronExpressionTest.java b/jodatime/src/test/java/fc/cron/CronExpressionTest.java index a9d10d8..46d7aa0 100644 --- a/jodatime/src/test/java/fc/cron/CronExpressionTest.java +++ b/jodatime/src/test/java/fc/cron/CronExpressionTest.java @@ -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; diff --git a/pom.xml b/pom.xml index 4d3be55..8a06423 100644 --- a/pom.xml +++ b/pom.xml @@ -1,10 +1,11 @@ 4.0.0 + fc.cron cron - jar - 1.3 + pom + 1.4-SNAPSHOT cron https://github.com/frode-carlsen/cron @@ -15,43 +16,20 @@ + + java8 + jodatime + + - - joda-time - joda-time - 2.3 - - - - org.easytesting - fest-assert - 1.4 - test - - junit junit - 4.11 + 4.12 test - - - - maven-compiler-plugin - 3.1 - - true - 1.7 - 1.7 - UTF-8 - - - - - UTF-8 UTF-8