From ead670801e736789763a5f4c90d0b5e32860705d Mon Sep 17 00:00:00 2001 From: Si Tang Date: Sun, 10 Oct 2021 22:31:40 +0900 Subject: [PATCH] #446 when dst overlaps, skip duplicate execution if cron is running less frequent than every hour --- .../model/time/SingleExecutionTime.java | 13 ++++++++---- src/test/java/com/cronutils/Issue446Test.java | 20 +++++++++---------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/cronutils/model/time/SingleExecutionTime.java b/src/main/java/com/cronutils/model/time/SingleExecutionTime.java index fe03df8d..6663cadf 100755 --- a/src/main/java/com/cronutils/model/time/SingleExecutionTime.java +++ b/src/main/java/com/cronutils/model/time/SingleExecutionTime.java @@ -28,7 +28,6 @@ import com.cronutils.utils.VisibleForTesting; import java.time.*; -import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoField; import java.time.temporal.ChronoUnit; import java.time.temporal.TemporalField; @@ -52,7 +51,6 @@ * Calculates execution time given a cron pattern. */ public class SingleExecutionTime implements ExecutionTime { - private static DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); private static final int MAX_ITERATIONS = 100_000; private static final LocalTime MAX_SECONDS = LocalTime.MAX.truncatedTo(SECONDS); @@ -107,8 +105,15 @@ public Optional nextExecution(final ZonedDateTime date) { if (nextMatch.equals(date)) { nextMatch = nextClosestMatch(date.plusSeconds(1)); - if(nextMatch.format(DATE_TIME_FORMATTER).equals(date.format(DATE_TIME_FORMATTER))){ // daylight saving case: issue #446 - nextMatch = nextClosestMatch(date.plusSeconds(1).plusHours(1)); + if (nextMatch.getOffset().compareTo(date.getOffset()) > 0) { + // daylight saving time overlap case: issue #446 + ZonedDateTime nextNextExecution = nextClosestMatch(nextMatch.plusSeconds(1)); + + boolean lessFrequentThan1Hour = (Duration.between(nextMatch, nextNextExecution).toHours() > 1); + if (lessFrequentThan1Hour) { + // Avoid duplicate execution during DST overlap + nextMatch = nextClosestMatch(date.plusSeconds(1).plusHours(1)); + } } } return Optional.of(nextMatch); diff --git a/src/test/java/com/cronutils/Issue446Test.java b/src/test/java/com/cronutils/Issue446Test.java index 42d31c82..3a9ab3f6 100644 --- a/src/test/java/com/cronutils/Issue446Test.java +++ b/src/test/java/com/cronutils/Issue446Test.java @@ -7,8 +7,9 @@ import com.cronutils.model.definition.CronDefinitionBuilder; import com.cronutils.model.time.ExecutionTime; import org.junit.Assert; -import org.junit.Ignore; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.time.*; import java.util.Optional; @@ -21,6 +22,7 @@ public class Issue446Test { + private static final Logger LOGGER = LoggerFactory.getLogger(Issue446Test.class); private static final CronDefinition definition = CronDefinitionBuilder.defineCron() .withMinutes().and() .withHours().and() @@ -38,7 +40,7 @@ public void testWrongIntervalsForEvery6Months() { LocalDateTime dayOfApril = LocalDateTime.of(2020, 4, 25, 0, 0); Clock clock = Clock.fixed(dayOfApril.toInstant(ZoneOffset.UTC), ZoneId.systemDefault()); ZonedDateTime dayOfAprilInLocalTimezone = ZonedDateTime.now(clock); - System.out.println("now: " + dayOfAprilInLocalTimezone); + LOGGER.info("now: " + dayOfAprilInLocalTimezone); Cron cron = getEveryMonthFromNow(dayOfAprilInLocalTimezone, 6).instance(); ZonedDateTime nextRun = nextRun(cron, dayOfAprilInLocalTimezone); // first run @@ -69,7 +71,7 @@ public void testDaylightSavingOverlapMinuteNextRun() { LocalDateTime daylightSaving2020 = LocalDateTime.of(2020, 10, 25, 1, 10); Clock clock = Clock.fixed(daylightSaving2020.toInstant(ZoneOffset.ofHours(2)),ZoneId.of("Europe/Rome")); ZonedDateTime daylightSaving2020InLocalTimezone = ZonedDateTime.now(clock); - System.out.println("\nnow: " + daylightSaving2020InLocalTimezone); + LOGGER.info("\nnow: " + daylightSaving2020InLocalTimezone); Cron cron = getEvery30Minute(daylightSaving2020InLocalTimezone).instance(); ZonedDateTime nextRun = nextRun(cron, daylightSaving2020InLocalTimezone); // first run @@ -101,13 +103,11 @@ public void testDaylightSavingOverlapMinuteNextRun() { } @Test - @Ignore public void testDaylightSavingOverlapHourNextRun() { - LocalDateTime startDay = LocalDateTime.of(2020, 10, 24, 1, 0); // Day before Daylight saving - Clock clock = Clock.fixed(startDay.toInstant(ZoneOffset.ofHours(2)),ZoneId.of("Europe/Rome")); + Clock clock = Clock.fixed(startDay.toInstant(ZoneOffset.ofHours(2)), ZoneId.of("Europe/Rome")); ZonedDateTime daylightSaving2020InLocalTimezone = ZonedDateTime.now(clock); - System.out.println("\nnow: " + daylightSaving2020InLocalTimezone); + LOGGER.info("\nnow: " + daylightSaving2020InLocalTimezone); Cron cron = getEveryHour(daylightSaving2020InLocalTimezone).instance(); ZonedDateTime nextRun = nextRun(cron, daylightSaving2020InLocalTimezone); @@ -120,9 +120,9 @@ public void testDaylightSavingOverlapHourNextRun() { Assert.assertEquals(ZoneOffset.ofHours(2), nextRun.getOffset()); startDay = LocalDateTime.of(2020, 10, 25, 1, 0); // Daylight saving - clock = Clock.fixed(startDay.toInstant(ZoneOffset.ofHours(2)),ZoneId.of("Europe/Rome")); + clock = Clock.fixed(startDay.toInstant(ZoneOffset.ofHours(2)), ZoneId.of("Europe/Rome")); daylightSaving2020InLocalTimezone = ZonedDateTime.now(clock); - System.out.println("\nnow: " + daylightSaving2020InLocalTimezone); + LOGGER.info("\nnow: " + daylightSaving2020InLocalTimezone); cron = getEveryHour(daylightSaving2020InLocalTimezone).instance(); nextRun = nextRun(cron, daylightSaving2020InLocalTimezone); // first run @@ -167,7 +167,7 @@ private static ZonedDateTime nextRun(Cron cron, ZonedDateTime when) { if (!next.isPresent()) { fail(); } - System.out.println("Calculated next run at " + next.get()); + LOGGER.info("Calculated next run at " + next.get()); return next.get(); } }