Skip to content

Commit 541802f

Browse files
authored
Merge pull request #490 from IndeedSi/446-fix-dst-overlap-behavior
#446 when dst overlaps, skip duplicate execution only if cron is running less frequent than every hour
2 parents 3a9837b + ead6708 commit 541802f

2 files changed

Lines changed: 19 additions & 14 deletions

File tree

src/main/java/com/cronutils/model/time/SingleExecutionTime.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import com.cronutils.utils.VisibleForTesting;
2929

3030
import java.time.*;
31-
import java.time.format.DateTimeFormatter;
3231
import java.time.temporal.ChronoField;
3332
import java.time.temporal.ChronoUnit;
3433
import java.time.temporal.TemporalField;
@@ -52,7 +51,6 @@
5251
* Calculates execution time given a cron pattern.
5352
*/
5453
public class SingleExecutionTime implements ExecutionTime {
55-
private static DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
5654
private static final int MAX_ITERATIONS = 100_000;
5755

5856
private static final LocalTime MAX_SECONDS = LocalTime.MAX.truncatedTo(SECONDS);
@@ -107,8 +105,15 @@ public Optional<ZonedDateTime> nextExecution(final ZonedDateTime date) {
107105
if (nextMatch.equals(date)) {
108106
nextMatch = nextClosestMatch(date.plusSeconds(1));
109107

110-
if(nextMatch.format(DATE_TIME_FORMATTER).equals(date.format(DATE_TIME_FORMATTER))){ // daylight saving case: issue #446
111-
nextMatch = nextClosestMatch(date.plusSeconds(1).plusHours(1));
108+
if (nextMatch.getOffset().compareTo(date.getOffset()) > 0) {
109+
// daylight saving time overlap case: issue #446
110+
ZonedDateTime nextNextExecution = nextClosestMatch(nextMatch.plusSeconds(1));
111+
112+
boolean lessFrequentThan1Hour = (Duration.between(nextMatch, nextNextExecution).toHours() > 1);
113+
if (lessFrequentThan1Hour) {
114+
// Avoid duplicate execution during DST overlap
115+
nextMatch = nextClosestMatch(date.plusSeconds(1).plusHours(1));
116+
}
112117
}
113118
}
114119
return Optional.of(nextMatch);

src/test/java/com/cronutils/Issue446Test.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
import com.cronutils.model.definition.CronDefinitionBuilder;
88
import com.cronutils.model.time.ExecutionTime;
99
import org.junit.Assert;
10-
import org.junit.Ignore;
1110
import org.junit.Test;
11+
import org.slf4j.Logger;
12+
import org.slf4j.LoggerFactory;
1213

1314
import java.time.*;
1415
import java.util.Optional;
@@ -21,6 +22,7 @@
2122

2223

2324
public class Issue446Test {
25+
private static final Logger LOGGER = LoggerFactory.getLogger(Issue446Test.class);
2426
private static final CronDefinition definition = CronDefinitionBuilder.defineCron()
2527
.withMinutes().and()
2628
.withHours().and()
@@ -38,7 +40,7 @@ public void testWrongIntervalsForEvery6Months() {
3840
LocalDateTime dayOfApril = LocalDateTime.of(2020, 4, 25, 0, 0);
3941
Clock clock = Clock.fixed(dayOfApril.toInstant(ZoneOffset.UTC), ZoneId.systemDefault());
4042
ZonedDateTime dayOfAprilInLocalTimezone = ZonedDateTime.now(clock);
41-
System.out.println("now: " + dayOfAprilInLocalTimezone);
43+
LOGGER.info("now: " + dayOfAprilInLocalTimezone);
4244
Cron cron = getEveryMonthFromNow(dayOfAprilInLocalTimezone, 6).instance();
4345

4446
ZonedDateTime nextRun = nextRun(cron, dayOfAprilInLocalTimezone); // first run
@@ -69,7 +71,7 @@ public void testDaylightSavingOverlapMinuteNextRun() {
6971
LocalDateTime daylightSaving2020 = LocalDateTime.of(2020, 10, 25, 1, 10);
7072
Clock clock = Clock.fixed(daylightSaving2020.toInstant(ZoneOffset.ofHours(2)),ZoneId.of("Europe/Rome"));
7173
ZonedDateTime daylightSaving2020InLocalTimezone = ZonedDateTime.now(clock);
72-
System.out.println("\nnow: " + daylightSaving2020InLocalTimezone);
74+
LOGGER.info("\nnow: " + daylightSaving2020InLocalTimezone);
7375
Cron cron = getEvery30Minute(daylightSaving2020InLocalTimezone).instance();
7476

7577
ZonedDateTime nextRun = nextRun(cron, daylightSaving2020InLocalTimezone); // first run
@@ -101,13 +103,11 @@ public void testDaylightSavingOverlapMinuteNextRun() {
101103
}
102104

103105
@Test
104-
@Ignore
105106
public void testDaylightSavingOverlapHourNextRun() {
106-
107107
LocalDateTime startDay = LocalDateTime.of(2020, 10, 24, 1, 0); // Day before Daylight saving
108-
Clock clock = Clock.fixed(startDay.toInstant(ZoneOffset.ofHours(2)),ZoneId.of("Europe/Rome"));
108+
Clock clock = Clock.fixed(startDay.toInstant(ZoneOffset.ofHours(2)), ZoneId.of("Europe/Rome"));
109109
ZonedDateTime daylightSaving2020InLocalTimezone = ZonedDateTime.now(clock);
110-
System.out.println("\nnow: " + daylightSaving2020InLocalTimezone);
110+
LOGGER.info("\nnow: " + daylightSaving2020InLocalTimezone);
111111
Cron cron = getEveryHour(daylightSaving2020InLocalTimezone).instance();
112112

113113
ZonedDateTime nextRun = nextRun(cron, daylightSaving2020InLocalTimezone);
@@ -120,9 +120,9 @@ public void testDaylightSavingOverlapHourNextRun() {
120120
Assert.assertEquals(ZoneOffset.ofHours(2), nextRun.getOffset());
121121

122122
startDay = LocalDateTime.of(2020, 10, 25, 1, 0); // Daylight saving
123-
clock = Clock.fixed(startDay.toInstant(ZoneOffset.ofHours(2)),ZoneId.of("Europe/Rome"));
123+
clock = Clock.fixed(startDay.toInstant(ZoneOffset.ofHours(2)), ZoneId.of("Europe/Rome"));
124124
daylightSaving2020InLocalTimezone = ZonedDateTime.now(clock);
125-
System.out.println("\nnow: " + daylightSaving2020InLocalTimezone);
125+
LOGGER.info("\nnow: " + daylightSaving2020InLocalTimezone);
126126
cron = getEveryHour(daylightSaving2020InLocalTimezone).instance();
127127

128128
nextRun = nextRun(cron, daylightSaving2020InLocalTimezone); // first run
@@ -167,7 +167,7 @@ private static ZonedDateTime nextRun(Cron cron, ZonedDateTime when) {
167167
if (!next.isPresent()) {
168168
fail();
169169
}
170-
System.out.println("Calculated next run at " + next.get());
170+
LOGGER.info("Calculated next run at " + next.get());
171171
return next.get();
172172
}
173173
}

0 commit comments

Comments
 (0)