Skip to content

Commit 9b8a851

Browse files
committed
Merge branch '7.0.x'
2 parents 4c6194a + 6467fca commit 9b8a851

3 files changed

Lines changed: 39 additions & 2 deletions

File tree

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,11 @@ private static ValueRange parseRange(String value, Type type) {
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,12 @@ private static ValueRange parseRange(String value, Type type) {
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+
}
203+
current = type().get(temporal);
195204
}
196205
}
197206
if (count >= CronExpression.MAX_ATTEMPTS) {

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.scheduling.support;
1818

19+
import java.time.ZonedDateTime;
1920
import java.util.Arrays;
2021

2122
import org.assertj.core.api.Condition;
@@ -29,6 +30,7 @@
2930
*
3031
* @author Arjen Poutsma
3132
* @author Sam Brannen
33+
* @author Brian Clozel
3234
*/
3335
class BitsCronFieldTests {
3436

@@ -112,6 +114,24 @@ void names() {
112114
.has(clear(0)).has(setRange(1, 7));
113115
}
114116

117+
@Test
118+
void nextOrSameWithMidnightGap() {
119+
BitsCronField field = BitsCronField.parseHours("0-23/2");
120+
ZonedDateTime last = ZonedDateTime.parse("2025-04-24T23:00:00+02:00[Africa/Cairo]");
121+
ZonedDateTime expected = ZonedDateTime.parse("2025-04-25T02:00:00+03:00[Africa/Cairo]");
122+
ZonedDateTime actual = field.nextOrSame(last);
123+
assertThat(actual).isEqualTo(expected);
124+
}
125+
126+
@Test
127+
void nextOrSameWithGapAfterRollForward() {
128+
BitsCronField field = BitsCronField.parseHours("0,2");
129+
ZonedDateTime last = ZonedDateTime.parse("2026-03-08T01:00:00-05:00[America/New_York]");
130+
ZonedDateTime expected = ZonedDateTime.parse("2026-03-09T00:00:00-04:00[America/New_York]");
131+
ZonedDateTime actual = field.nextOrSame(last);
132+
assertThat(actual).isEqualTo(expected);
133+
}
134+
115135

116136
private static Condition<BitsCronField> set(int... indices) {
117137
return new Condition<>(String.format("set bits %s", Arrays.toString(indices))) {

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)