Support macros in CronExpression
This commit introduces supports for macros like "@yearly", "@monthly", etc. in CronExpression. Closes gh-25471
This commit is contained in:
@@ -41,6 +41,16 @@ public final class CronExpression {
|
||||
|
||||
static final int MAX_ATTEMPTS = 366;
|
||||
|
||||
private static final String[] MACROS = new String[] {
|
||||
"@yearly", "0 0 0 1 1 *",
|
||||
"@annually", "0 0 0 1 1 *",
|
||||
"@monthly", "0 0 0 1 * *",
|
||||
"@weekly", "0 0 0 * * 0",
|
||||
"@daily", "0 0 0 * * *",
|
||||
"@midnight", "0 0 0 * * *",
|
||||
"@hourly", "0 0 * * * *"
|
||||
};
|
||||
|
||||
|
||||
private final CronField[] fields;
|
||||
|
||||
@@ -111,6 +121,15 @@ public final class CronExpression {
|
||||
* <li>{@code "0 0 0 25 12 ?"} = every Christmas Day at midnight</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>The following macros are also supported:
|
||||
* <ul>
|
||||
* <li>{@code "@yearly"} (or {@code "@annually"}) to run un once a year, i.e. {@code "0 0 0 1 1 *"},</li>
|
||||
* <li>{@code "@monthly"} to run once a month, i.e. {@code "0 0 0 1 * *"},</li>
|
||||
* <li>{@code "@weekly"} to run once a week, i.e. {@code "0 0 0 * * 0"},</li>
|
||||
* <li>{@code "@daily"} (or {@code "@midnight"}) to run once a day, i.e. {@code "0 0 0 * * *"},</li>
|
||||
* <li>{@code "@hourly"} to run once an hour, i.e. {@code "0 0 * * * *"}.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param expression the expression string to parse
|
||||
* @return the parsed {@code CronExpression} object
|
||||
* @throws IllegalArgumentException in the expression does not conform to
|
||||
@@ -119,6 +138,8 @@ public final class CronExpression {
|
||||
public static CronExpression parse(String expression) {
|
||||
Assert.hasLength(expression, "Expression string must not be empty");
|
||||
|
||||
expression = resolveMacros(expression);
|
||||
|
||||
String[] fields = StringUtils.tokenizeToStringArray(expression, " ");
|
||||
if (fields.length != 6) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
@@ -141,6 +162,17 @@ public final class CronExpression {
|
||||
}
|
||||
|
||||
|
||||
private static String resolveMacros(String expression) {
|
||||
expression = expression.trim();
|
||||
for (int i = 0; i < MACROS.length; i = i + 2) {
|
||||
if (MACROS[i].equalsIgnoreCase(expression)) {
|
||||
return MACROS[i + 1];
|
||||
}
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the next {@link Temporal} that matches this expression.
|
||||
* @param temporal the seed value
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import static java.time.DayOfWeek.FRIDAY;
|
||||
import static java.time.DayOfWeek.MONDAY;
|
||||
import static java.time.DayOfWeek.SUNDAY;
|
||||
import static java.time.DayOfWeek.TUESDAY;
|
||||
import static java.time.DayOfWeek.WEDNESDAY;
|
||||
import static java.time.temporal.TemporalAdjusters.next;
|
||||
@@ -462,4 +463,124 @@ class CronExpressionTests {
|
||||
assertThat(actual.getDayOfMonth()).isEqualTo(13);
|
||||
}
|
||||
|
||||
@Test
|
||||
void yearly() {
|
||||
CronExpression expression = CronExpression.parse("@yearly");
|
||||
assertThat(expression).isEqualTo(CronExpression.parse("0 0 0 1 1 *"));
|
||||
|
||||
LocalDateTime last = LocalDateTime.now().withMonth(10).withDayOfMonth(10);
|
||||
LocalDateTime expected = LocalDateTime.of(last.getYear() + 1, 1, 1, 0, 0);
|
||||
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
expected = expected.plusYears(1);
|
||||
actual = expression.next(last);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
expected = expected.plusYears(1);
|
||||
assertThat(expression.next(last)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void annually() {
|
||||
CronExpression expression = CronExpression.parse("@annually");
|
||||
assertThat(expression).isEqualTo(CronExpression.parse("0 0 0 1 1 *"));
|
||||
assertThat(expression).isEqualTo(CronExpression.parse("@yearly"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void monthly() {
|
||||
CronExpression expression = CronExpression.parse("@monthly");
|
||||
assertThat(expression).isEqualTo(CronExpression.parse("0 0 0 1 * *"));
|
||||
|
||||
LocalDateTime last = LocalDateTime.now().withMonth(10).withDayOfMonth(10);
|
||||
LocalDateTime expected = LocalDateTime.of(last.getYear(), 11, 1, 0, 0);
|
||||
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
expected = expected.plusMonths(1);
|
||||
actual = expression.next(last);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
expected = expected.plusMonths(1);
|
||||
assertThat(expression.next(last)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void weekly() {
|
||||
CronExpression expression = CronExpression.parse("@weekly");
|
||||
assertThat(expression).isEqualTo(CronExpression.parse("0 0 0 * * 0"));
|
||||
|
||||
LocalDateTime last = LocalDateTime.now();
|
||||
LocalDateTime expected = last.with(next(SUNDAY)).withHour(0).withMinute(0).withSecond(0).withNano(0);
|
||||
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
expected = expected.plusWeeks(1);
|
||||
actual = expression.next(last);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
expected = expected.plusWeeks(1);
|
||||
assertThat(expression.next(last)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void daily() {
|
||||
CronExpression expression = CronExpression.parse("@daily");
|
||||
assertThat(expression).isEqualTo(CronExpression.parse("0 0 0 * * *"));
|
||||
|
||||
LocalDateTime last = LocalDateTime.now();
|
||||
LocalDateTime expected = last.plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
|
||||
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
expected = expected.plusDays(1);
|
||||
actual = expression.next(last);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
expected = expected.plusDays(1);
|
||||
assertThat(expression.next(last)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void midnight() {
|
||||
CronExpression expression = CronExpression.parse("@midnight");
|
||||
assertThat(expression).isEqualTo(CronExpression.parse("0 0 0 * * *"));
|
||||
assertThat(expression).isEqualTo(CronExpression.parse("@daily"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void hourly() {
|
||||
CronExpression expression = CronExpression.parse("@hourly");
|
||||
assertThat(expression).isEqualTo(CronExpression.parse("0 0 * * * *"));
|
||||
|
||||
LocalDateTime last = LocalDateTime.now();
|
||||
LocalDateTime expected = last.plusHours(1).withMinute(0).withSecond(0).withNano(0);
|
||||
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
expected = expected.plusHours(1);
|
||||
actual = expression.next(last);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
expected = expected.plusHours(1);
|
||||
assertThat(expression.next(last)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user