diff --git a/hutool-cron/src/main/java/org/dromara/hutool/cron/pattern/CronPattern.java b/hutool-cron/src/main/java/org/dromara/hutool/cron/pattern/CronPattern.java index bcbd37795..09cf74e11 100644 --- a/hutool-cron/src/main/java/org/dromara/hutool/cron/pattern/CronPattern.java +++ b/hutool-cron/src/main/java/org/dromara/hutool/cron/pattern/CronPattern.java @@ -191,7 +191,7 @@ public class CronPattern { /** * 给定时间是否匹配定时任务表达式 * - * @param fields 时间字段值,{second, minute, hour, dayOfMonth, month, dayOfWeek, year} + * @param fields 时间字段值,{second, minute, hour, dayOfMonth, monthBase1, dayOfWeekBase0, year} * @return 如果匹配返回 {@code true}, 否则返回 {@code false} */ private boolean match(final int[] fields) { @@ -206,7 +206,7 @@ public class CronPattern { /** * 获取下一个最近的匹配日期时间 * - * @param values 时间字段值,{second, minute, hour, dayOfMonth, month, dayOfWeek, year} + * @param values 时间字段值,{second, minute, hour, dayOfMonth, monthBase1, dayOfWeekBase0, year} * @param zone 时区 * @return {@link Calendar},毫秒数为0 */ diff --git a/hutool-cron/src/main/java/org/dromara/hutool/cron/pattern/matcher/BoolArrayMatcher.java b/hutool-cron/src/main/java/org/dromara/hutool/cron/pattern/matcher/BoolArrayMatcher.java index 3956f16cc..11e4a54a5 100644 --- a/hutool-cron/src/main/java/org/dromara/hutool/cron/pattern/matcher/BoolArrayMatcher.java +++ b/hutool-cron/src/main/java/org/dromara/hutool/cron/pattern/matcher/BoolArrayMatcher.java @@ -29,8 +29,8 @@ public class BoolArrayMatcher implements PartMatcher { /** * 用户定义此字段的最小值 */ - private final int minValue; - private final boolean[] bValues; + protected final int minValue; + protected final boolean[] bValues; /** * 构造 @@ -50,6 +50,7 @@ public class BoolArrayMatcher implements PartMatcher { @Override public boolean test(final Integer value) { + final boolean[] bValues = this.bValues; if (null == value || value >= bValues.length) { return false; } @@ -58,7 +59,9 @@ public class BoolArrayMatcher implements PartMatcher { @Override public int nextAfter(int value) { + final int minValue = this.minValue; if(value > minValue){ + final boolean[] bValues = this.bValues; while(value < bValues.length){ if(bValues[value]){ return value; diff --git a/hutool-cron/src/main/java/org/dromara/hutool/cron/pattern/matcher/DayOfMonthMatcher.java b/hutool-cron/src/main/java/org/dromara/hutool/cron/pattern/matcher/DayOfMonthMatcher.java index 1012b00fa..2ddc1e6f3 100644 --- a/hutool-cron/src/main/java/org/dromara/hutool/cron/pattern/matcher/DayOfMonthMatcher.java +++ b/hutool-cron/src/main/java/org/dromara/hutool/cron/pattern/matcher/DayOfMonthMatcher.java @@ -43,23 +43,72 @@ public class DayOfMonthMatcher extends BoolArrayMatcher { */ public boolean match(final int value, final int month, final boolean isLeapYear) { return (super.test(value) // 在约定日范围内的某一天 - //匹配器中用户定义了最后一天(31表示最后一天) - || (value > 27 && test(31) && isLastDayOfMonth(value, month, isLeapYear))); + //匹配器中用户定义了最后一天(31表示最后一天) + || matchLastDay(value, getLastDay(month, isLeapYear))); } /** - * 是否为本月最后一天,规则如下: + * 获取指定值之后的匹配值,也可以是指定值本身
+ * 如果表达式中存在最后一天(如使用"L"),则: + * + * + * @param value 指定的值 + * @param month 月份,从1开始 + * @param isLeapYear 是否为闰年 + * @return 匹配到的值或之后的值 + */ + public int nextAfter(int value, final int month, final boolean isLeapYear) { + final int minValue = this.minValue; + if (value > minValue) { + final boolean[] bValues = this.bValues; + final int lastDay = getLastDay(month, isLeapYear); + while (value < lastDay) { + if (bValues[value]) { + return value; + } + value++; + } + + // value == lastDay + if(test(31)){ + // 匹配当月最后一天 + return value; + } + } + + // 两种情况返回最小值 + // 一是给定值小于最小值,那下一个匹配值就是最小值 + // 二是给定值大于最大值,那下一个匹配值也是下一轮的最小值 + return minValue; + } + + /** + * 是否匹配本月最后一天,规则如下: *
 	 * 1、闰年2月匹配是否为29
 	 * 2、其它月份是否匹配最后一天的日期(可能为30或者31)
+	 * 3、表达式包含最后一天(使用31表示)
 	 * 
* * @param value 被检查的值 - * @param month 月份,从1开始 - * @param isLeapYear 是否闰年 + * @param lastDay 月份的最后一天 * @return 是否为本月最后一天 */ - private static boolean isLastDayOfMonth(final int value, final int month, final boolean isLeapYear) { - return value == Month.getLastDay(month - 1, isLeapYear); + private boolean matchLastDay(final int value, final int lastDay) { + return value > 27 && test(31) && value == lastDay; + } + + /** + * 获取最后一天 + * + * @param month 月,base1 + * @param isLeapYear 是否闰年 + * @return 最后一天 + */ + private static int getLastDay(final int month, final boolean isLeapYear) { + return Month.getLastDay(month - 1, isLeapYear); } } diff --git a/hutool-cron/src/main/java/org/dromara/hutool/cron/pattern/matcher/PatternMatcher.java b/hutool-cron/src/main/java/org/dromara/hutool/cron/pattern/matcher/PatternMatcher.java index 01a7deaf4..5acde90b6 100644 --- a/hutool-cron/src/main/java/org/dromara/hutool/cron/pattern/matcher/PatternMatcher.java +++ b/hutool-cron/src/main/java/org/dromara/hutool/cron/pattern/matcher/PatternMatcher.java @@ -12,6 +12,8 @@ package org.dromara.hutool.cron.pattern.matcher; +import org.dromara.hutool.core.date.DateUtil; +import org.dromara.hutool.core.lang.Console; import org.dromara.hutool.cron.pattern.Part; import java.time.Year; @@ -52,13 +54,13 @@ public class PatternMatcher { final PartMatcher yearMatcher) { matchers = new PartMatcher[]{ - secondMatcher, - minuteMatcher, - hourMatcher, - dayOfMonthMatcher, - monthMatcher, - dayOfWeekMatcher, - yearMatcher + secondMatcher, + minuteMatcher, + hourMatcher, + dayOfMonthMatcher, + monthMatcher, + dayOfWeekMatcher, + yearMatcher }; } @@ -109,12 +111,12 @@ public class PatternMatcher { */ private boolean match(final int second, final int minute, final int hour, final int dayOfMonth, final int month, final int dayOfWeek, final int year) { return ((second < 0) || matchers[0].test(second)) // 匹配秒(非秒匹配模式下始终返回true) - && matchers[1].test(minute)// 匹配分 - && matchers[2].test(hour)// 匹配时 - && matchDayOfMonth(matchers[3], dayOfMonth, month, Year.isLeap(year))// 匹配日 - && matchers[4].test(month) // 匹配月 - && matchers[5].test(dayOfWeek)// 匹配周 - && matchers[6].test(year);// 匹配年 + && matchers[1].test(minute)// 匹配分 + && matchers[2].test(hour)// 匹配时 + && matchDayOfMonth(matchers[3], dayOfMonth, month, Year.isLeap(year))// 匹配日 + && matchers[4].test(month) // 匹配月 + && matchers[5].test(dayOfWeek)// 匹配周 + && matchers[6].test(year);// 匹配年 } /** @@ -128,8 +130,8 @@ public class PatternMatcher { */ private static boolean matchDayOfMonth(final PartMatcher matcher, final int dayOfMonth, final int month, final boolean isLeapYear) { return ((matcher instanceof DayOfMonthMatcher) // - ? ((DayOfMonthMatcher) matcher).match(dayOfMonth, month, isLeapYear) // - : matcher.test(dayOfMonth)); + ? ((DayOfMonthMatcher) matcher).match(dayOfMonth, month, isLeapYear) // + : matcher.test(dayOfMonth)); } //endregion @@ -145,11 +147,11 @@ public class PatternMatcher { * * *
-	 *        秒 分 时 日 月 周 年
+	 *        秒 分 时 日 月(1) 周(0) 年
 	 *     下 <-----------------> 上
 	 * 
* - * @param values 时间字段值,{second, minute, hour, dayOfMonth, month, dayOfWeek, year} + * @param values 时间字段值,{second, minute, hour, dayOfMonth, monthBase1, dayOfWeekBase0, year} * @param zone 时区 * @return {@link Calendar},毫秒数为0 */ @@ -182,7 +184,7 @@ public class PatternMatcher { * 下 <-----------------> 上 * * - * @param values 时间字段值,{second, minute, hour, dayOfMonth, month, dayOfWeek, year} + * @param values 时间字段值,{second, minute, hour, dayOfMonth, monthBase1, dayOfWeekBase0, year} * @return {@link Calendar},毫秒数为0 */ private int[] nextMatchValuesAfter(final int[] values) { @@ -197,7 +199,15 @@ public class PatternMatcher { i--; continue; } - nextValue = matchers[i].nextAfter(values[i]); + + if (i == Part.DAY_OF_MONTH.ordinal()) { + final boolean isLeapYear = DateUtil.isLeapYear(newValues[Part.YEAR.ordinal()]); + final int month = values[Part.MONTH.ordinal()]; + nextValue = ((DayOfMonthMatcher) matchers[i]).nextAfter(values[i], month, isLeapYear); + } else { + nextValue = matchers[i].nextAfter(values[i]); + } + if (nextValue > values[i]) { // 此部分正常获取新值,结束循环,后续的部分置最小值 newValues[i] = nextValue; @@ -209,6 +219,7 @@ public class PatternMatcher { nextValue = -1;// 标记回退查找 break; } + // 值不变,检查下一个部分 i--; } @@ -221,7 +232,15 @@ public class PatternMatcher { i++; continue; } - nextValue = matchers[i].nextAfter(values[i] + 1); + + if (i == Part.DAY_OF_MONTH.ordinal()) { + final boolean isLeapYear = DateUtil.isLeapYear(newValues[Part.YEAR.ordinal()]); + final int month = values[Part.MONTH.ordinal()]; + nextValue = ((DayOfMonthMatcher) matchers[i]).nextAfter(values[i] + 1, month, isLeapYear); + } else { + nextValue = matchers[i].nextAfter(values[i] + 1); + } + if (nextValue > values[i]) { newValues[i] = nextValue; i--; diff --git a/hutool-cron/src/test/java/org/dromara/hutool/cron/pattern/IssueI92H5HTest.java b/hutool-cron/src/test/java/org/dromara/hutool/cron/pattern/IssueI92H5HTest.java new file mode 100644 index 000000000..c633e96eb --- /dev/null +++ b/hutool-cron/src/test/java/org/dromara/hutool/cron/pattern/IssueI92H5HTest.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024. looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * https://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.cron.pattern; + +import org.dromara.hutool.core.date.DateTime; +import org.dromara.hutool.core.date.DateUtil; +import org.junit.jupiter.api.Test; + +import java.util.Calendar; + +public class IssueI92H5HTest { + @Test + void nextMatchAfterTest() { + // 匹配所有月,返回下一月 + final DateTime date = DateUtil.parse("2022-04-08 07:44:16"); + final CronPattern pattern = new CronPattern("0 0 0 L 2 ?"); + //noinspection ConstantConditions + final Calendar calendar = pattern.nextMatchAfter(date.toCalendar()); + System.out.println(DateUtil.date(calendar)); + } +}