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>
This commit is contained in:
arno
2026-06-03 15:16:16 +08:00
committed by Brian Clozel
parent 2e653246b5
commit cddc671a8c
2 changed files with 18 additions and 2 deletions
@@ -177,7 +177,11 @@ final class BitsCronField extends CronField {
int next = nextSetBit(current);
if (next == -1) {
temporal = type().rollForward(temporal);
next = nextSetBit(0);
next = nextSetBit(type().get(temporal));
if (next == -1) {
temporal = type().rollForward(temporal);
next = nextSetBit(0);
}
}
if (next == current) {
return temporal;
@@ -191,7 +195,11 @@ final class BitsCronField extends CronField {
next = nextSetBit(current);
if (next == -1) {
temporal = type().rollForward(temporal);
next = nextSetBit(0);
next = nextSetBit(type().get(temporal));
if (next == -1) {
temporal = type().rollForward(temporal);
next = nextSetBit(0);
}
}
}
if (count >= CronExpression.MAX_ATTEMPTS) {
@@ -1367,6 +1367,14 @@ class CronExpressionTests {
actual = cronExpression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
cronExpression = CronExpression.parse("0 0 */2 * * ?");
last = ZonedDateTime.parse("2025-04-24T22:00:00+02:00[Africa/Cairo]");
expected = ZonedDateTime.parse("2025-04-25T02:00:00+03:00[Africa/Cairo]");
actual = cronExpression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
}
@Test