Skip to content

Commit 7e2106b

Browse files
committed
Refactor roll forward in CronField
Before this commit, CronField.Type::rollForward added temporal units to reach the higher order field. This caused issues with DST, where the added amount of hours was either too small or too large. This commit refactors the implementation so that it now adds one to the higher order field, and reset the current field to the minimum value. Closes gh-28095
1 parent 453c6d4 commit 7e2106b

File tree

2 files changed

+23
-19
lines changed

2 files changed

+23
-19
lines changed

spring-context/src/main/java/org/springframework/scheduling/support/CronField.java

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.time.DateTimeException;
2020
import java.time.temporal.ChronoField;
21+
import java.time.temporal.ChronoUnit;
2122
import java.time.temporal.Temporal;
2223
import java.time.temporal.ValueRange;
2324
import java.util.function.BiFunction;
@@ -168,22 +169,25 @@ protected static <T extends Temporal & Comparable<? super T>> T cast(Temporal te
168169
* day-of-month, month, day-of-week.
169170
*/
170171
protected enum Type {
171-
NANO(ChronoField.NANO_OF_SECOND),
172-
SECOND(ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND),
173-
MINUTE(ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND),
174-
HOUR(ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND),
175-
DAY_OF_MONTH(ChronoField.DAY_OF_MONTH, ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND),
176-
MONTH(ChronoField.MONTH_OF_YEAR, ChronoField.DAY_OF_MONTH, ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND),
177-
DAY_OF_WEEK(ChronoField.DAY_OF_WEEK, ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND);
172+
NANO(ChronoField.NANO_OF_SECOND, ChronoUnit.SECONDS),
173+
SECOND(ChronoField.SECOND_OF_MINUTE, ChronoUnit.MINUTES, ChronoField.NANO_OF_SECOND),
174+
MINUTE(ChronoField.MINUTE_OF_HOUR, ChronoUnit.HOURS, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND),
175+
HOUR(ChronoField.HOUR_OF_DAY, ChronoUnit.DAYS, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND),
176+
DAY_OF_MONTH(ChronoField.DAY_OF_MONTH, ChronoUnit.MONTHS, ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND),
177+
MONTH(ChronoField.MONTH_OF_YEAR, ChronoUnit.YEARS, ChronoField.DAY_OF_MONTH, ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND),
178+
DAY_OF_WEEK(ChronoField.DAY_OF_WEEK, ChronoUnit.WEEKS, ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND);
178179

179180

180181
private final ChronoField field;
181182

183+
private final ChronoUnit higherOrder;
184+
182185
private final ChronoField[] lowerOrders;
183186

184187

185-
Type(ChronoField field, ChronoField... lowerOrders) {
188+
Type(ChronoField field, ChronoUnit higherOrder, ChronoField... lowerOrders) {
186189
this.field = field;
190+
this.higherOrder = higherOrder;
187191
this.lowerOrders = lowerOrders;
188192
}
189193

@@ -266,17 +270,9 @@ public <T extends Temporal & Comparable<? super T>> T elapseUntil(T temporal, in
266270
* @return the rolled forward temporal
267271
*/
268272
public <T extends Temporal & Comparable<? super T>> T rollForward(T temporal) {
269-
int current = get(temporal);
270-
ValueRange range = temporal.range(this.field);
271-
long amount = range.getMaximum() - current + 1;
272-
T result = this.field.getBaseUnit().addTo(temporal, amount);
273-
current = get(result);
274-
range = result.range(this.field);
275-
// adjust for daylight savings
276-
if (current != range.getMinimum()) {
277-
result = this.field.adjustInto(result, range.getMinimum());
278-
}
279-
return result;
273+
T result = this.higherOrder.addTo(temporal, 1);
274+
ValueRange range = result.range(this.field);
275+
return this.field.adjustInto(result, range.getMinimum());
280276
}
281277

282278
/**

spring-context/src/test/java/org/springframework/scheduling/support/CronExpressionTests.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1344,6 +1344,14 @@ public void daylightSaving() {
13441344
actual = cronExpression.next(last);
13451345
assertThat(actual).isNotNull();
13461346
assertThat(actual).isEqualTo(expected);
1347+
1348+
cronExpression = CronExpression.parse("0 5 0 * * *");
1349+
1350+
last = ZonedDateTime.parse("2019-10-27T01:05+02:00[Europe/Amsterdam]");
1351+
expected = ZonedDateTime.parse("2019-10-28T00:05+01:00[Europe/Amsterdam]");
1352+
actual = cronExpression.next(last);
1353+
assertThat(actual).isNotNull();
1354+
assertThat(actual).isEqualTo(expected);
13471355
}
13481356

13491357
@Test

0 commit comments

Comments
 (0)