From 8f9a1cdc0c2ab2bee112dddad00a94bb501a3181 Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Tue, 1 Feb 2022 13:51:40 +0100 Subject: [PATCH] Consider current date in "1W" cron expressions Prior to this commit, the QuartzCronField::weekdayNearestTo would elapse until the next month before checking if the current day matched. After this commit, the current day is checked before we elapse until the next month. Closes gh-27966 --- .../scheduling/support/QuartzCronField.java | 39 ++++++++++--------- .../support/CronExpressionTests.java | 25 ++++++++++-- 2 files changed, 43 insertions(+), 21 deletions(-) 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 0b72a273ab..36c70c0883 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 @@ -251,43 +251,46 @@ final class QuartzCronField extends CronField { private static TemporalAdjuster weekdayNearestTo(int dayOfMonth) { return temporal -> { int current = Type.DAY_OF_MONTH.get(temporal); - int dayOfWeek = temporal.get(ChronoField.DAY_OF_WEEK); + DayOfWeek dayOfWeek = DayOfWeek.from(temporal); - if ((current == dayOfMonth && dayOfWeek < 6) || // dayOfMonth is a weekday - (dayOfWeek == 5 && current == dayOfMonth - 1) || // dayOfMonth is a Saturday, so Friday before - (dayOfWeek == 1 && current == dayOfMonth + 1) || // dayOfMonth is a Sunday, so Monday after - (dayOfWeek == 1 && dayOfMonth == 1 && current == 3)) { // dayOfMonth is the 1st, so Monday 3rd + if ((current == dayOfMonth && isWeekday(dayOfWeek)) || // dayOfMonth is a weekday + (dayOfWeek == DayOfWeek.FRIDAY && current == dayOfMonth - 1) || // dayOfMonth is a Saturday, so Friday before + (dayOfWeek == DayOfWeek.MONDAY && current == dayOfMonth + 1) || // dayOfMonth is a Sunday, so Monday after + (dayOfWeek == DayOfWeek.MONDAY && dayOfMonth == 1 && current == 3)) { // dayOfMonth is Saturday 1st, so Monday 3rd return temporal; } int count = 0; while (count++ < CronExpression.MAX_ATTEMPTS) { - temporal = Type.DAY_OF_MONTH.elapseUntil(cast(temporal), dayOfMonth); - temporal = atMidnight().adjustInto(temporal); - current = Type.DAY_OF_MONTH.get(temporal); if (current == dayOfMonth) { - dayOfWeek = temporal.get(ChronoField.DAY_OF_WEEK); + dayOfWeek = DayOfWeek.from(temporal); - if (dayOfWeek == 6) { // Saturday + if (dayOfWeek == DayOfWeek.SATURDAY) { if (dayOfMonth != 1) { - return temporal.minus(1, ChronoUnit.DAYS); + temporal = temporal.minus(1, ChronoUnit.DAYS); } else { - // exception for "1W" fields: execute on nearest Monday - return temporal.plus(2, ChronoUnit.DAYS); + // exception for "1W" fields: execute on next Monday + temporal = temporal.plus(2, ChronoUnit.DAYS); } } - else if (dayOfWeek == 7) { // Sunday - return temporal.plus(1, ChronoUnit.DAYS); - } - else { - return temporal; + else if (dayOfWeek == DayOfWeek.SUNDAY) { + temporal = temporal.plus(1, ChronoUnit.DAYS); } + return atMidnight().adjustInto(temporal); + } + else { + temporal = Type.DAY_OF_MONTH.elapseUntil(cast(temporal), dayOfMonth); + current = Type.DAY_OF_MONTH.get(temporal); } } return null; }; } + private static boolean isWeekday(DayOfWeek dayOfWeek) { + return dayOfWeek != DayOfWeek.SATURDAY && dayOfWeek != DayOfWeek.SUNDAY; + } + /** * Return a temporal adjuster that finds the last of the given doy-of-week * in a month. 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 3a21f984aa..5abce9e2de 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 @@ -16,13 +16,13 @@ package org.springframework.scheduling.support; +import java.time.DayOfWeek; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.Year; import java.time.ZoneId; import java.time.ZonedDateTime; -import java.time.temporal.ChronoField; import java.time.temporal.Temporal; import org.assertj.core.api.Condition; @@ -30,6 +30,7 @@ import org.junit.jupiter.api.Test; import static java.time.DayOfWeek.FRIDAY; import static java.time.DayOfWeek.MONDAY; +import static java.time.DayOfWeek.SATURDAY; import static java.time.DayOfWeek.SUNDAY; import static java.time.DayOfWeek.THURSDAY; import static java.time.DayOfWeek.TUESDAY; @@ -46,8 +47,8 @@ class CronExpressionTests { @Override public boolean matches(Temporal value) { - int dayOfWeek = value.get(ChronoField.DAY_OF_WEEK); - return dayOfWeek != 6 && dayOfWeek != 7; + DayOfWeek dayOfWeek = DayOfWeek.from(value); + return dayOfWeek != SATURDAY && dayOfWeek != SUNDAY; } }; @@ -958,6 +959,24 @@ class CronExpressionTests { assertThat(actual).isNotNull(); assertThat(actual).isEqualTo(expected); assertThat(actual).is(weekday); + + last = LocalDateTime.of(2022, 1, 1, 0, 0); + assertThat(last.getDayOfWeek()).isEqualTo(SATURDAY); + expected = LocalDateTime.of(2022, 1, 3, 0, 0); + assertThat(expected.getDayOfWeek()).isEqualTo(MONDAY); + actual = expression.next(last); + assertThat(actual).isNotNull(); + assertThat(actual).isEqualTo(expected); + assertThat(actual).is(weekday); + + last = LocalDateTime.of(2021, 8, 1, 0,0); + assertThat(last.getDayOfWeek()).isEqualTo(SUNDAY); + expected = LocalDateTime.of(2021, 8, 2, 0, 0); + assertThat(expected.getDayOfWeek()).isEqualTo(MONDAY); + actual = expression.next(last); + assertThat(actual).isNotNull(); + assertThat(actual).isEqualTo(expected); + assertThat(actual).is(weekday); } @Test