+ * "At 8:00am every Monday through Friday" or "At 1:30am every
+ * last Friday of the month".
+ *
* Cron expressions are comprised of 6 required fields and one optional field
* separated by white space. The fields respectively are described as follows:
- *
- *
+ *
+ *
+ * Examples of cron expressions and their meanings.
*
- * | Field Name |
- * |
- * Allowed Values |
- * |
- * Allowed Special Characters |
+ * Field Name |
+ * |
+ * Allowed Values |
+ * |
+ * Allowed Special Characters |
*
*
- * Seconds |
- *
- * | 0-59 |
- *
- * | , - * / |
+ * Seconds |
+ * |
+ * 0-59 |
+ * |
+ * , - * / |
*
*
- * Minutes |
- *
- * | 0-59 |
- *
- * | , - * / |
+ * Minutes |
+ * |
+ * 0-59 |
+ * |
+ * , - * / |
*
*
- * Hours |
- *
- * | 0-23 |
- *
- * | , - * / |
+ * Hours |
+ * |
+ * 0-23 |
+ * |
+ * , - * / |
*
*
- * Day-of-month |
- *
- * | 1-31 |
- *
- * | , - * ? / L W |
+ * Day-of-month |
+ * |
+ * 1-31 |
+ * |
+ * , - * ? / L W |
*
*
- * Month |
- *
- * | 0-11 or JAN-DEC |
- *
- * | , - * / |
+ * Month |
+ * |
+ * 1-12 or JAN-DEC |
+ * |
+ * , - * / |
*
*
- * Day-of-Week |
- *
- * | 1-7 or SUN-SAT |
- *
- * | , - * ? / L # |
+ * Day-of-Week |
+ * |
+ * 1-7 or SUN-SAT |
+ * |
+ * , - * ? / L # |
*
*
- * Year (Optional) |
- *
- * | empty, 1970-2199 |
- *
- * | , - * / |
+ * Year (Optional) |
+ * |
+ * empty, 1970-2199 |
+ * |
+ * , - * / |
*
*
- *
- * The '*' character is used to specify all values. For example, "*"
+ *
+ * The '*' character is used to specify all values. For example, "*"
* in the minute field means "every minute".
- *
+ *
+ *
* The '?' character is allowed for the day-of-month and day-of-week fields. It
* is used to specify 'no specific value'. This is useful when you need to
* specify something in one of the two fields, but not the other.
- *
+ *
* The '-' character is used to specify ranges For example "10-12" in
* the hour field means "the hours 10, 11 and 12".
- *
+ *
* The ',' character is used to specify additional values. For example
* "MON,WED,FRI" in the day-of-week field means "the days Monday,
* Wednesday, and Friday".
- *
+ *
+ *
* The '/' character is used to specify increments. For example "0/15"
- * in the seconds field means "the seconds 0, 15, 30, and 45". And
+ * in the seconds field means "the seconds 0, 15, 30, and 45". And
* "5/15" in the seconds field means "the seconds 5, 20, 35, and
* 50". Specifying '*' before the '/' is equivalent to specifying 0 is
* the value to start with. Essentially, for each field in the expression, there
- * is a set of numbers that can be turned on or off. For seconds and minutes,
+ * is a set of numbers that can be turned on or off. For seconds and minutes,
* the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to
* 31, and for months 0 to 11 (JAN to DEC). The "/" character simply helps you turn
* on every "nth" value in the given set. Thus "7/6" in the
- * month field only turns on month "7", it does NOT mean every 6th
- * month, please note that subtlety.
- *
+ * month field only turns on month "7", it does NOT mean every 6th
+ * month, please note that subtlety.
+ *
+ *
* The 'L' character is allowed for the day-of-month and day-of-week fields.
- * This character is short-hand for "last", but it has different
- * meaning in each of the two fields. For example, the value "L" in
- * the day-of-month field means "the last day of the month" - day 31
- * for January, day 28 for February on non-leap years. If used in the
- * day-of-week field by itself, it simply means "7" or
+ * This character is short-hand for "last", but it has different
+ * meaning in each of the two fields. For example, the value "L" in
+ * the day-of-month field means "the last day of the month" - day 31
+ * for January, day 28 for February on non-leap years. If used in the
+ * day-of-week field by itself, it simply means "7" or
* "SAT". But if used in the day-of-week field after another value, it
* means "the last xxx day of the month" - for example "6L"
- * means "the last friday of the month". You can also specify an offset
- * from the last day of the month, such as "L-3" which would mean the third-to-last
- * day of the calendar month. When using the 'L' option, it is important not to
+ * means "the last friday of the month". You can also specify an offset
+ * from the last day of the month, such as "L-3" which would mean the third-to-last
+ * day of the calendar month. When using the 'L' option, it is important not to
* specify lists, or ranges of values, as you'll get confusing/unexpected results.
- *
- * The 'W' character is allowed for the day-of-month field. This character
- * is used to specify the weekday (Monday-Friday) nearest the given day. As an
- * example, if you were to specify "15W" as the value for the
+ *
+ *
+ * The 'W' character is allowed for the day-of-month field. This character
+ * is used to specify the weekday (Monday-Friday) nearest the given day. As an
+ * example, if you were to specify "15W" as the value for the
* day-of-month field, the meaning is: "the nearest weekday to the 15th of
- * the month". So if the 15th is a Saturday, the trigger will fire on
+ * the month". So if the 15th is a Saturday, the trigger will fire on
* Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the
- * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th.
+ * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th.
* However if you specify "1W" as the value for day-of-month, and the
- * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not
- * 'jump' over the boundary of a month's days. The 'W' character can only be
+ * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not
+ * 'jump' over the boundary of a month's days. The 'W' character can only be
* specified when the day-of-month is a single day, not a range or list of days.
- *
- * The 'L' and 'W' characters can also be combined for the day-of-month
- * expression to yield 'LW', which translates to "last weekday of the
+ *
+ *
+ * The 'L' and 'W' characters can also be combined for the day-of-month
+ * expression to yield 'LW', which translates to "last weekday of the
* month".
- *
+ *
+ *
* The '#' character is allowed for the day-of-week field. This character is
- * used to specify "the nth" XXX day of the month. For example, the
- * value of "6#3" in the day-of-week field means the third Friday of
- * the month (day 6 = Friday and "#3" = the 3rd one in the month).
- * Other examples: "2#1" = the first Monday of the month and
+ * used to specify "the nth" XXX day of the month. For example, the
+ * value of "6#3" in the day-of-week field means the third Friday of
+ * the month (day 6 = Friday and "#3" = the 3rd one in the month).
+ * Other examples: "2#1" = the first Monday of the month and
* "4#5" = the fifth Wednesday of the month. Note that if you specify
* "#5" and there is not 5 of the given day-of-week in the month, then
* no firing will occur that month. If the '#' character is used, there can
- * only be one expression in the day-of-week field ("3#1,6#3" is
+ * only be one expression in the day-of-week field ("3#1,6#3" is
* not valid, since there are two expressions).
- *
+ *
*
- *
+ *
* The legal characters and the names of months and days of the week are not
* case sensitive.
- *
+ *
*
* NOTES:
+ *
*
* - Support for specifying both a day-of-week and a day-of-month value is
* not complete (you'll need to use the '?' character in one of these fields).
*
- * - Overflowing ranges is supported - that is, having a larger number on
- * the left hand side than the right. You might do 22-2 to catch 10 o'clock
- * at night until 2 o'clock in the morning, or you might have NOV-FEB. It is
- * very important to note that overuse of overflowing ranges creates ranges
- * that don't make sense and no effort has been made to determine which
- * interpretation CronExpression chooses. An example would be
+ *
- Overflowing ranges is supported - that is, having a larger number on
+ * the left hand side than the right. You might do 22-2 to catch 10 o'clock
+ * at night until 2 o'clock in the morning, or you might have NOV-FEB. It is
+ * very important to note that overuse of overflowing ranges creates ranges
+ * that don't make sense and no effort has been made to determine which
+ * interpretation CronExpression chooses. An example would be
* "0 0 14-6 ? * FRI-MON".
*
- *
- *
- *
+ *
+ *
* @author Sharada Jambula, James House
* @author Contributions from Mads Henderson
* @author Refactoring from CronTrigger to CronExpression by Aaron Craven
- *
- * Borrowed from quartz v2.3.1
- *
*/
public final class CronExpression implements Serializable, Cloneable {
private static final long serialVersionUID = 12423409423L;
-
+
protected static final int SECOND = 0;
protected static final int MINUTE = 1;
protected static final int HOUR = 2;
@@ -212,11 +218,14 @@ public final class CronExpression implements Serializable, Cloneable {
protected static final int YEAR = 6;
protected static final int ALL_SPEC_INT = 99; // '*'
protected static final int NO_SPEC_INT = 98; // '?'
+ protected static final int MAX_LAST_DAY_OFFSET = 30;
+ protected static final int LAST_DAY_OFFSET_START = 32; // "L-30"
+ protected static final int LAST_DAY_OFFSET_END = LAST_DAY_OFFSET_START + MAX_LAST_DAY_OFFSET; // 'L'
protected static final Integer ALL_SPEC = ALL_SPEC_INT;
protected static final Integer NO_SPEC = NO_SPEC_INT;
-
- protected static final Map monthMap = new HashMap(20);
- protected static final Map dayMap = new HashMap(60);
+
+ protected static final Map monthMap = new HashMap<>(20);
+ protected static final Map dayMap = new HashMap<>(60);
static {
monthMap.put("JAN", 0);
monthMap.put("FEB", 1);
@@ -246,43 +255,41 @@ public final class CronExpression implements Serializable, Cloneable {
protected transient TreeSet minutes;
protected transient TreeSet hours;
protected transient TreeSet daysOfMonth;
+ protected transient TreeSet nearestWeekdays;
protected transient TreeSet months;
protected transient TreeSet daysOfWeek;
protected transient TreeSet years;
- protected transient boolean lastdayOfWeek = false;
- protected transient int nthdayOfWeek = 0;
- protected transient boolean lastdayOfMonth = false;
- protected transient boolean nearestWeekday = false;
- protected transient int lastdayOffset = 0;
+ protected transient boolean lastDayOfWeek = false;
+ protected transient int nthDayOfWeek = 0;
protected transient boolean expressionParsed = false;
-
+
public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100;
/**
- * Constructs a new CronExpression based on the specified
+ * Constructs a new CronExpression based on the specified
* parameter.
- *
+ *
* @param cronExpression String representation of the cron expression the
* new object should represent
* @throws java.text.ParseException
- * if the string expression cannot be parsed into a valid
+ * if the string expression cannot be parsed into a valid
* CronExpression
*/
public CronExpression(String cronExpression) throws ParseException {
if (cronExpression == null) {
throw new IllegalArgumentException("cronExpression cannot be null");
}
-
+
this.cronExpression = cronExpression.toUpperCase(Locale.US);
-
+
buildExpression(this.cronExpression);
}
-
+
/**
* Constructs a new {@code CronExpression} as a copy of an existing
* instance.
- *
+ *
* @param expression
* The existing cron expression to be copied
*/
@@ -296,7 +303,7 @@ public final class CronExpression implements Serializable, Cloneable {
try {
buildExpression(cronExpression);
} catch (ParseException ex) {
- throw new AssertionError();
+ throw new AssertionError("Could not parse expression!", ex);
}
if (expression.getTimeZone() != null) {
setTimeZone((TimeZone) expression.getTimeZone().clone());
@@ -307,7 +314,7 @@ public final class CronExpression implements Serializable, Cloneable {
* Indicates whether the given date satisfies the cron expression. Note that
* milliseconds are ignored, so two Dates falling on different milliseconds
* of the same second will always have the same result here.
- *
+ *
* @param date the date to evaluate
* @return a boolean indicating whether the given date satisfies the cron
* expression
@@ -317,18 +324,18 @@ public final class CronExpression implements Serializable, Cloneable {
testDateCal.setTime(date);
testDateCal.set(Calendar.MILLISECOND, 0);
Date originalDate = testDateCal.getTime();
-
+
testDateCal.add(Calendar.SECOND, -1);
-
+
Date timeAfter = getTimeAfter(testDateCal.getTime());
return ((timeAfter != null) && (timeAfter.equals(originalDate)));
}
-
+
/**
* Returns the next date/time after the given date/time which
* satisfies the cron expression.
- *
+ *
* @param date the date/time at which to begin the search for the next valid
* date/time
* @return the next valid date/time
@@ -336,48 +343,48 @@ public final class CronExpression implements Serializable, Cloneable {
public Date getNextValidTimeAfter(Date date) {
return getTimeAfter(date);
}
-
+
/**
* Returns the next date/time after the given date/time which does
* not satisfy the expression
- *
- * @param date the date/time at which to begin the search for the next
+ *
+ * @param date the date/time at which to begin the search for the next
* invalid date/time
* @return the next valid date/time
*/
public Date getNextInvalidTimeAfter(Date date) {
long difference = 1000;
-
+
//move back to the nearest second so differences will be accurate
Calendar adjustCal = Calendar.getInstance(getTimeZone());
adjustCal.setTime(date);
adjustCal.set(Calendar.MILLISECOND, 0);
Date lastDate = adjustCal.getTime();
-
+
Date newDate;
-
+
//FUTURE_TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very bad here, depending on the cron expression. It is, however A solution.
-
+
//keep getting the next included time until it's farther than one second
// apart. At that point, lastDate is the last valid fire time. We return
// the second immediately following it.
while (difference == 1000) {
newDate = getTimeAfter(lastDate);
- if(newDate == null) {
+ if(newDate == null)
break;
- }
+
difference = newDate.getTime() - lastDate.getTime();
-
+
if (difference == 1000) {
lastDate = newDate;
}
}
-
+
return new Date(lastDate.getTime() + 1000);
}
-
+
/**
- * Returns the time zone for which this CronExpression
+ * Returns the time zone for which this CronExpression
* will be resolved.
*/
public TimeZone getTimeZone() {
@@ -389,16 +396,16 @@ public final class CronExpression implements Serializable, Cloneable {
}
/**
- * Sets the time zone for which this CronExpression
+ * Sets the time zone for which this CronExpression
* will be resolved.
*/
public void setTimeZone(TimeZone timeZone) {
this.timeZone = timeZone;
}
-
+
/**
* Returns the string representation of the CronExpression
- *
+ *
* @return a string representation of the CronExpression
*/
@Override
@@ -407,30 +414,30 @@ public final class CronExpression implements Serializable, Cloneable {
}
/**
- * Indicates whether the specified cron expression can be parsed into a
+ * Indicates whether the specified cron expression can be parsed into a
* valid cron expression
- *
+ *
* @param cronExpression the expression to evaluate
* @return a boolean indicating whether the given expression is a valid cron
* expression
*/
public static boolean isValidExpression(String cronExpression) {
-
+
try {
new CronExpression(cronExpression);
} catch (ParseException pe) {
return false;
}
-
+
return true;
}
public static void validateExpression(String cronExpression) throws ParseException {
-
+
new CronExpression(cronExpression);
}
-
-
+
+
////////////////////////////////////////////////////////////////////////////
//
// Expression Parsing Functions
@@ -443,25 +450,28 @@ public final class CronExpression implements Serializable, Cloneable {
try {
if (seconds == null) {
- seconds = new TreeSet();
+ seconds = new TreeSet<>();
}
if (minutes == null) {
- minutes = new TreeSet();
+ minutes = new TreeSet<>();
}
if (hours == null) {
- hours = new TreeSet();
+ hours = new TreeSet<>();
}
if (daysOfMonth == null) {
- daysOfMonth = new TreeSet();
+ daysOfMonth = new TreeSet<>();
+ }
+ if (nearestWeekdays == null) {
+ nearestWeekdays = new TreeSet<>();
}
if (months == null) {
- months = new TreeSet();
+ months = new TreeSet<>();
}
if (daysOfWeek == null) {
- daysOfWeek = new TreeSet