Skip to content

Commit 96b4494

Browse files
arnocursoragent
authored andcommitted
Fix CronExpression day skip on midnight DST gap
After rollForward, BitsCronField always searched for the next matching bit from zero. When daylight saving creates a gap at the start of a period (e.g. Africa/Cairo), the temporal lands on a non-zero field value and matching from zero could advance an entire period too far, skipping the calendar day. Search from the actual field value in the new period instead, falling back to zero only when no bit matches in that period. See gh-36865 Signed-off-by: arno <me@zmovo.com> Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 2555655 commit 96b4494

2 files changed

Lines changed: 18 additions & 2 deletions

File tree

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,11 @@ public <T extends Temporal & Comparable<? super T>> T nextOrSame(T temporal) {
177177
int next = nextSetBit(current);
178178
if (next == -1) {
179179
temporal = type().rollForward(temporal);
180-
next = nextSetBit(0);
180+
next = nextSetBit(type().get(temporal));
181+
if (next == -1) {
182+
temporal = type().rollForward(temporal);
183+
next = nextSetBit(0);
184+
}
181185
}
182186
if (next == current) {
183187
return temporal;
@@ -191,7 +195,11 @@ public <T extends Temporal & Comparable<? super T>> T nextOrSame(T temporal) {
191195
next = nextSetBit(current);
192196
if (next == -1) {
193197
temporal = type().rollForward(temporal);
194-
next = nextSetBit(0);
198+
next = nextSetBit(type().get(temporal));
199+
if (next == -1) {
200+
temporal = type().rollForward(temporal);
201+
next = nextSetBit(0);
202+
}
195203
}
196204
}
197205
if (count >= CronExpression.MAX_ATTEMPTS) {

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
@@ -1367,6 +1367,14 @@ void daylightSaving() {
13671367
actual = cronExpression.next(last);
13681368
assertThat(actual).isNotNull();
13691369
assertThat(actual).isEqualTo(expected);
1370+
1371+
cronExpression = CronExpression.parse("0 0 */2 * * ?");
1372+
1373+
last = ZonedDateTime.parse("2025-04-24T22:00:00+02:00[Africa/Cairo]");
1374+
expected = ZonedDateTime.parse("2025-04-25T02:00:00+03:00[Africa/Cairo]");
1375+
actual = cronExpression.next(last);
1376+
assertThat(actual).isNotNull();
1377+
assertThat(actual).isEqualTo(expected);
13701378
}
13711379

13721380
@Test

0 commit comments

Comments
 (0)