Skip to content

Commit 27bfcbb

Browse files
committed
Fix daylight saving issue in CronExpression
Closes gh-26830
1 parent b153b5e commit 27bfcbb

File tree

4 files changed

+24
-16
lines changed

4 files changed

+24
-16
lines changed

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,11 @@ protected Type type() {
157157
return this.type;
158158
}
159159

160+
@SuppressWarnings("unchecked")
161+
protected static <T extends Temporal & Comparable<? super T>> T cast(Temporal temporal) {
162+
return (T) temporal;
163+
}
164+
160165

161166
/**
162167
* Represents the type of cron field, i.e. seconds, minutes, hours,
@@ -236,16 +241,17 @@ public int checkValidValue(int value) {
236241
*/
237242
public <T extends Temporal & Comparable<? super T>> T elapseUntil(T temporal, int goal) {
238243
int current = get(temporal);
244+
ValueRange range = temporal.range(this.field);
239245
if (current < goal) {
240-
T result = this.field.getBaseUnit().addTo(temporal, goal - current);
241-
current = get(result);
242-
if (current > goal) { // can occur due to daylight saving, see gh-26744
243-
result = this.field.getBaseUnit().addTo(result, goal - current);
246+
if (range.isValidIntValue(goal)) {
247+
return cast(temporal.with(this.field, goal));
248+
}
249+
else {
250+
// goal is invalid, eg. 29th Feb, lets try to get as close as possible
251+
return this.field.getBaseUnit().addTo(temporal, goal - current);
244252
}
245-
return result;
246253
}
247254
else {
248-
ValueRange range = temporal.range(this.field);
249255
long amount = goal + range.getMaximum() - current + 1 - range.getMinimum();
250256
return this.field.getBaseUnit().addTo(temporal, amount);
251257
}

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

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -326,12 +326,6 @@ private static Temporal rollbackToMidnight(Temporal current, Temporal result) {
326326
}
327327
}
328328

329-
@SuppressWarnings("unchecked")
330-
private static <T extends Temporal & Comparable<? super T>> T cast(Temporal temporal) {
331-
return (T) temporal;
332-
}
333-
334-
335329
@Override
336330
public <T extends Temporal & Comparable<? super T>> T nextOrSame(T temporal) {
337331
T result = adjust(temporal);

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
@@ -1276,6 +1276,14 @@ public void daylightSaving() {
12761276
actual = cronExpression.next(last);
12771277
assertThat(actual).isNotNull();
12781278
assertThat(actual).isEqualTo(expected);
1279+
1280+
cronExpression = CronExpression.parse("0 10 2 * * *");
1281+
1282+
last = ZonedDateTime.parse("2013-03-31T01:09:00+01:00[Europe/Amsterdam]");
1283+
expected = ZonedDateTime.parse("2013-04-01T02:10:00+02:00[Europe/Amsterdam]");
1284+
actual = cronExpression.next(last);
1285+
assertThat(actual).isNotNull();
1286+
assertThat(actual).isEqualTo(expected);
12791287
}
12801288

12811289

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -822,13 +822,13 @@ void daylightSavingMissingHour(LocalDateTime localDateTime, TimeZone timeZone) {
822822

823823
// 2:00 AM on March 31, 2013: start of Daylight Saving Time for CET in 2013.
824824
// Setting up last completion:
825-
// - PST: Sun Mar 31 10:10:54 CEST 2013
826-
// - CET: Sun Mar 31 01:10:54 CET 2013
825+
// - PST: Sun Mar 31 10:09:54 CEST 2013
826+
// - CET: Sun Mar 31 01:09:54 CET 2013
827827
this.calendar.set(Calendar.DAY_OF_MONTH, 31);
828828
this.calendar.set(Calendar.MONTH, Calendar.MARCH);
829829
this.calendar.set(Calendar.YEAR, 2013);
830830
this.calendar.set(Calendar.HOUR_OF_DAY, 1);
831-
this.calendar.set(Calendar.MINUTE, 10); // changing to any minute from 0-9 causes the test to fail for CET.
831+
this.calendar.set(Calendar.MINUTE, 9);
832832
this.calendar.set(Calendar.SECOND, 54);
833833
Date lastCompletionTime = this.calendar.getTime();
834834

0 commit comments

Comments
 (0)