From 27bfcbbc823897169942ddc9a9ff9538486426c5 Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Tue, 20 Apr 2021 16:25:34 +0200 Subject: [PATCH] Fix daylight saving issue in CronExpression Closes gh-26830 --- .../scheduling/support/CronField.java | 18 ++++++++++++------ .../scheduling/support/QuartzCronField.java | 8 +------- .../support/CronExpressionTests.java | 8 ++++++++ .../scheduling/support/CronTriggerTests.java | 6 +++--- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/scheduling/support/CronField.java b/spring-context/src/main/java/org/springframework/scheduling/support/CronField.java index d5dee884d6..0d9ac6ceff 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/support/CronField.java +++ b/spring-context/src/main/java/org/springframework/scheduling/support/CronField.java @@ -157,6 +157,11 @@ abstract class CronField { return this.type; } + @SuppressWarnings("unchecked") + protected static > T cast(Temporal temporal) { + return (T) temporal; + } + /** * Represents the type of cron field, i.e. seconds, minutes, hours, @@ -236,16 +241,17 @@ abstract class CronField { */ public > T elapseUntil(T temporal, int goal) { int current = get(temporal); + ValueRange range = temporal.range(this.field); if (current < goal) { - T result = this.field.getBaseUnit().addTo(temporal, goal - current); - current = get(result); - if (current > goal) { // can occur due to daylight saving, see gh-26744 - result = this.field.getBaseUnit().addTo(result, goal - current); + if (range.isValidIntValue(goal)) { + return cast(temporal.with(this.field, goal)); + } + else { + // goal is invalid, eg. 29th Feb, lets try to get as close as possible + return this.field.getBaseUnit().addTo(temporal, goal - current); } - return result; } else { - ValueRange range = temporal.range(this.field); long amount = goal + range.getMaximum() - current + 1 - range.getMinimum(); return this.field.getBaseUnit().addTo(temporal, amount); } diff --git a/spring-context/src/main/java/org/springframework/scheduling/support/QuartzCronField.java b/spring-context/src/main/java/org/springframework/scheduling/support/QuartzCronField.java index 8a3c5ba67e..d656ab77fd 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/support/QuartzCronField.java +++ b/spring-context/src/main/java/org/springframework/scheduling/support/QuartzCronField.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -326,12 +326,6 @@ final class QuartzCronField extends CronField { } } - @SuppressWarnings("unchecked") - private static > T cast(Temporal temporal) { - return (T) temporal; - } - - @Override public > T nextOrSame(T temporal) { T result = adjust(temporal); diff --git a/spring-context/src/test/java/org/springframework/scheduling/support/CronExpressionTests.java b/spring-context/src/test/java/org/springframework/scheduling/support/CronExpressionTests.java index aea49716d8..b4457c9e09 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/support/CronExpressionTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/support/CronExpressionTests.java @@ -1276,6 +1276,14 @@ class CronExpressionTests { actual = cronExpression.next(last); assertThat(actual).isNotNull(); assertThat(actual).isEqualTo(expected); + + cronExpression = CronExpression.parse("0 10 2 * * *"); + + last = ZonedDateTime.parse("2013-03-31T01:09:00+01:00[Europe/Amsterdam]"); + expected = ZonedDateTime.parse("2013-04-01T02:10:00+02:00[Europe/Amsterdam]"); + actual = cronExpression.next(last); + assertThat(actual).isNotNull(); + assertThat(actual).isEqualTo(expected); } diff --git a/spring-context/src/test/java/org/springframework/scheduling/support/CronTriggerTests.java b/spring-context/src/test/java/org/springframework/scheduling/support/CronTriggerTests.java index af1891d61d..1fe501b130 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/support/CronTriggerTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/support/CronTriggerTests.java @@ -822,13 +822,13 @@ class CronTriggerTests { // 2:00 AM on March 31, 2013: start of Daylight Saving Time for CET in 2013. // Setting up last completion: - // - PST: Sun Mar 31 10:10:54 CEST 2013 - // - CET: Sun Mar 31 01:10:54 CET 2013 + // - PST: Sun Mar 31 10:09:54 CEST 2013 + // - CET: Sun Mar 31 01:09:54 CET 2013 this.calendar.set(Calendar.DAY_OF_MONTH, 31); this.calendar.set(Calendar.MONTH, Calendar.MARCH); this.calendar.set(Calendar.YEAR, 2013); this.calendar.set(Calendar.HOUR_OF_DAY, 1); - this.calendar.set(Calendar.MINUTE, 10); // changing to any minute from 0-9 causes the test to fail for CET. + this.calendar.set(Calendar.MINUTE, 9); this.calendar.set(Calendar.SECOND, 54); Date lastCompletionTime = this.calendar.getTime(); -- GitLab