diff --git a/spring-context/src/main/java/org/springframework/scheduling/support/CronExpression.java b/spring-context/src/main/java/org/springframework/scheduling/support/CronExpression.java index 120ced6132..f3845847a3 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/support/CronExpression.java +++ b/spring-context/src/main/java/org/springframework/scheduling/support/CronExpression.java @@ -66,8 +66,9 @@ public final class CronExpression { CronField daysOfWeek, String expression) { + // reverse order, to make big changes first // to make sure we end up at 0 nanos, we add an extra field - this.fields = new CronField[]{CronField.zeroNanos(), seconds, minutes, hours, daysOfMonth, months, daysOfWeek}; + this.fields = new CronField[]{daysOfWeek, months, daysOfMonth, hours, minutes, seconds, CronField.zeroNanos()}; this.expression = expression; } diff --git a/spring-context/src/main/java/org/springframework/scheduling/support/CronField.java b/spring-context/src/main/java/org/springframework/scheduling/support/CronField.java index 0d9ac6ceff..bed9506634 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/support/CronField.java +++ b/spring-context/src/main/java/org/springframework/scheduling/support/CronField.java @@ -230,9 +230,7 @@ abstract class CronField { * Elapse the given temporal for the difference between the current * value of this field and the goal value. Typically, the returned * temporal will have the given goal as the current value for this type, - * but this is not the case for {@link #DAY_OF_MONTH}. For instance, - * if {@code goal} is 31, and {@code temporal} is April 16th, - * this method returns May 1st, because April 31st does not exist. + * but this is not the case for {@link #DAY_OF_MONTH}. * @param temporal the temporal to elapse * @param goal the goal value * @param the type of temporal @@ -247,8 +245,9 @@ abstract class CronField { return cast(temporal.with(this.field, goal)); } else { - // goal is invalid, eg. 29th Feb, lets try to get as close as possible - return this.field.getBaseUnit().addTo(temporal, goal - current); + // goal is invalid, eg. 29th Feb, so roll forward + long amount = range.getMaximum() - current + 1; + return this.field.getBaseUnit().addTo(temporal, amount); } } else { diff --git a/spring-context/src/main/java/org/springframework/scheduling/support/QuartzCronField.java b/spring-context/src/main/java/org/springframework/scheduling/support/QuartzCronField.java index d656ab77fd..0b72a273ab 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/support/QuartzCronField.java +++ b/spring-context/src/main/java/org/springframework/scheduling/support/QuartzCronField.java @@ -334,6 +334,9 @@ final class QuartzCronField extends CronField { // We ended up before the start, roll forward and try again temporal = this.rollForwardType.rollForward(temporal); result = adjust(temporal); + if (result != null) { + result = type().reset(result); + } } } return result; diff --git a/spring-context/src/test/java/org/springframework/scheduling/support/CronExpressionTests.java b/spring-context/src/test/java/org/springframework/scheduling/support/CronExpressionTests.java index 104e482828..3a21f984aa 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/support/CronExpressionTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/support/CronExpressionTests.java @@ -1319,6 +1319,21 @@ class CronExpressionTests { assertThat(actual).isEqualTo(expected); } + @Test + public void various() { + CronExpression cronExpression = CronExpression.parse("3-57 13-28 17,18 1,15 3-12 6#1"); + LocalDateTime last = LocalDateTime.of(2022, 9, 15, 17, 44, 11); + LocalDateTime expected = LocalDateTime.of(2022, 10, 1, 17, 13, 3); + LocalDateTime actual = cronExpression.next(last); + assertThat(actual).isNotNull(); + assertThat(actual).isEqualTo(expected); + cronExpression = CronExpression.parse("*/28 56 22 */6 * *"); + last = LocalDateTime.of(2022, 2, 27, 8, 0, 42); + expected = LocalDateTime.of(2022, 3, 1, 22, 56, 0); + actual = cronExpression.next(last); + assertThat(actual).isNotNull(); + assertThat(actual).isEqualTo(expected); + } }