diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index cb2b90277..696f256a6 100755 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -43,5 +43,11 @@ hutool-setting ${project.parent.version} + + org.quartz-scheduler + quartz + 2.3.2 + test + 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 09cf74e11..6e5072792 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 @@ -12,7 +12,7 @@ package org.dromara.hutool.cron.pattern; -import org.dromara.hutool.core.collection.CollUtil; +import org.dromara.hutool.core.comparator.CompareUtil; import org.dromara.hutool.core.date.CalendarUtil; import org.dromara.hutool.cron.pattern.matcher.PatternMatcher; import org.dromara.hutool.cron.pattern.parser.PatternParser; @@ -156,7 +156,14 @@ public class CronPattern { * @param calendar 时间 * @return 匹配到的下一个时间 */ - public Calendar nextMatchAfter(final Calendar calendar) { + public Calendar nextMatchAfter(Calendar calendar) { + // issue#I9FQUA,当提供的时间已经匹配表达式时,增加1秒以匹配下一个时间 + if(match(calendar, true)){ + final Calendar newCalendar = Calendar.getInstance(calendar.getTimeZone()); + newCalendar.setTimeInMillis(calendar.getTimeInMillis() + 1000); + calendar = newCalendar; + } + Calendar next = nextMatchAfter(PatternUtil.getFields(calendar, true), calendar.getTimeZone()); if (!match(next, true)) { next.set(Calendar.DAY_OF_MONTH, next.get(Calendar.DAY_OF_MONTH) + 1); @@ -211,11 +218,15 @@ public class CronPattern { * @return {@link Calendar},毫秒数为0 */ private Calendar nextMatchAfter(final int[] values, final TimeZone zone) { - final List nextMatches = new ArrayList<>(matchers.size()); + Calendar minMatch = null; for (final PatternMatcher matcher : matchers) { - nextMatches.add(matcher.nextMatchAfter(values, zone)); + if(null == minMatch){ + minMatch = matcher.nextMatchAfter(values, zone); + }else{ + minMatch = CompareUtil.min(minMatch, matcher.nextMatchAfter(values, zone)); + } } // 返回匹配到的最早日期 - return CollUtil.min(nextMatches); + return minMatch; } } diff --git a/hutool-cron/src/main/java/org/dromara/hutool/cron/pattern/CronPatternUtil.java b/hutool-cron/src/main/java/org/dromara/hutool/cron/pattern/CronPatternUtil.java index 103139834..a8d232ec9 100644 --- a/hutool-cron/src/main/java/org/dromara/hutool/cron/pattern/CronPatternUtil.java +++ b/hutool-cron/src/main/java/org/dromara/hutool/cron/pattern/CronPatternUtil.java @@ -12,12 +12,12 @@ package org.dromara.hutool.cron.pattern; -import org.dromara.hutool.core.collection.CollUtil; -import org.dromara.hutool.core.date.DateUnit; +import org.dromara.hutool.core.date.CalendarUtil; import org.dromara.hutool.core.date.DateUtil; import org.dromara.hutool.core.lang.Assert; import java.util.ArrayList; +import java.util.Calendar; import java.util.Date; import java.util.List; @@ -34,16 +34,11 @@ public class CronPatternUtil { * * @param pattern 表达式 * @param start 起始时间 - * @param isMatchSecond 是否匹配秒 * @return 日期 * @since 4.5.8 */ - public static Date nextDateAfter(final CronPattern pattern, final Date start, final boolean isMatchSecond) { - final List matchedDates = matchedDates(pattern, start.getTime(), DateUtil.endOfYear(start).getTime(), 1, isMatchSecond); - if (CollUtil.isNotEmpty(matchedDates)) { - return matchedDates.get(0); - } - return null; + public static Date nextDateAfter(final CronPattern pattern, final Date start) { + return DateUtil.date(pattern.nextMatchAfter(CalendarUtil.calendar(start))); } /** @@ -52,11 +47,10 @@ public class CronPatternUtil { * @param patternStr 表达式字符串 * @param start 起始时间 * @param count 列举数量 - * @param isMatchSecond 是否匹配秒 * @return 日期列表 */ - public static List matchedDates(final String patternStr, final Date start, final int count, final boolean isMatchSecond) { - return matchedDates(patternStr, start, DateUtil.endOfYear(start), count, isMatchSecond); + public static List matchedDates(final String patternStr, final Date start, final int count) { + return matchedDates(patternStr, start, DateUtil.endOfYear(start), count); } /** @@ -66,11 +60,10 @@ public class CronPatternUtil { * @param start 起始时间 * @param end 结束时间 * @param count 列举数量 - * @param isMatchSecond 是否匹配秒 * @return 日期列表 */ - public static List matchedDates(final String patternStr, final Date start, final Date end, final int count, final boolean isMatchSecond) { - return matchedDates(patternStr, start.getTime(), end.getTime(), count, isMatchSecond); + public static List matchedDates(final String patternStr, final Date start, final Date end, final int count) { + return matchedDates(patternStr, start.getTime(), end.getTime(), count); } /** @@ -80,11 +73,10 @@ public class CronPatternUtil { * @param start 起始时间 * @param end 结束时间 * @param count 列举数量 - * @param isMatchSecond 是否匹配秒 * @return 日期列表 */ - public static List matchedDates(final String patternStr, final long start, final long end, final int count, final boolean isMatchSecond) { - return matchedDates(new CronPattern(patternStr), start, end, count, isMatchSecond); + public static List matchedDates(final String patternStr, final long start, final long end, final int count) { + return matchedDates(new CronPattern(patternStr), start, end, count); } /** @@ -94,22 +86,22 @@ public class CronPatternUtil { * @param start 起始时间 * @param end 结束时间 * @param count 列举数量 - * @param isMatchSecond 是否匹配秒 * @return 日期列表 */ - public static List matchedDates(final CronPattern pattern, final long start, final long end, final int count, final boolean isMatchSecond) { + public static List matchedDates(final CronPattern pattern, final long start, final long end, final int count) { Assert.isTrue(start < end, "Start date is later than end !"); final List result = new ArrayList<>(count); - final long step = isMatchSecond ? DateUnit.SECOND.getMillis() : DateUnit.MINUTE.getMillis(); - for (long i = start; i < end; i += step) { - if (pattern.match(i, isMatchSecond)) { - result.add(DateUtil.date(i)); - if (result.size() >= count) { - break; - } + + Calendar calendar = pattern.nextMatchAfter(CalendarUtil.calendar(start)); + while(calendar.getTimeInMillis() < end){ + result.add(DateUtil.date(calendar)); + if(result.size() >= count){ + break; } + calendar = pattern.nextMatchAfter(calendar); } + return result; } } 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 d97a29aae..a73d28abf 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 @@ -150,7 +150,8 @@ public class PatternMatcher { * 下 <-----------------> 上 * * - * @param values 时间字段值,{second, minute, hour, dayOfMonth, monthBase1, dayOfWeekBase0, year} + * @param values 时间字段值,{second, minute, hour, dayOfMonth, monthBase1, dayOfWeekBase0, year}, + * 注意这个字段值会被修改 * @param zone 时区 * @return {@link Calendar},毫秒数为0 */ @@ -187,8 +188,6 @@ public class PatternMatcher { * @return {@link Calendar},毫秒数为0 */ private int[] nextMatchValuesAfter(final int[] values) { - final int[] newValues = values.clone(); - int i = Part.YEAR.ordinal(); // 新值,-1表示标识为回退 int nextValue = 0; @@ -199,11 +198,11 @@ public class PatternMatcher { continue; } - nextValue = getNextMatch(newValues, i, 0); + nextValue = getNextMatch(values, i, 0); if (nextValue > values[i]) { // 此部分正常获取新值,结束循环,后续的部分置最小值 - newValues[i] = nextValue; + values[i] = nextValue; i--; break; } else if (nextValue < values[i]) { @@ -226,10 +225,10 @@ public class PatternMatcher { continue; } - nextValue = getNextMatch(newValues, i, 1); + nextValue = getNextMatch(values, i, 1); if (nextValue > values[i]) { - newValues[i] = nextValue; + values[i] = nextValue; i--; break; } @@ -238,8 +237,8 @@ public class PatternMatcher { } // 修改值以下的字段全部归最小值 - setToMin(newValues, i); - return newValues; + setToMin(values, i); + return values; } /** diff --git a/hutool-cron/src/test/java/org/dromara/hutool/cron/pattern/CronPatternNextMatchTest.java b/hutool-cron/src/test/java/org/dromara/hutool/cron/pattern/CronPatternNextMatchTest.java index 1e413ba2d..f77ffb39f 100644 --- a/hutool-cron/src/test/java/org/dromara/hutool/cron/pattern/CronPatternNextMatchTest.java +++ b/hutool-cron/src/test/java/org/dromara/hutool/cron/pattern/CronPatternNextMatchTest.java @@ -28,7 +28,7 @@ public class CronPatternNextMatchTest { CronPattern pattern = new CronPattern("* * * * * * *"); DateTime date = DateUtil.truncate(DateUtil.now(), DateField.SECOND); Calendar calendar = pattern.nextMatchAfter(date.toCalendar()); - Assertions.assertEquals(date.getTime(), DateUtil.date(calendar).getTime()); + Assertions.assertEquals(date.getTime() + 1000, DateUtil.date(calendar).getTime()); // 匹配所有分,返回下一分钟 pattern = new CronPattern("0 * * * * * *"); diff --git a/hutool-cron/src/test/java/org/dromara/hutool/cron/pattern/CronPatternUtilTest.java b/hutool-cron/src/test/java/org/dromara/hutool/cron/pattern/CronPatternUtilTest.java index b8da48737..83847b772 100644 --- a/hutool-cron/src/test/java/org/dromara/hutool/cron/pattern/CronPatternUtilTest.java +++ b/hutool-cron/src/test/java/org/dromara/hutool/cron/pattern/CronPatternUtilTest.java @@ -24,7 +24,7 @@ public class CronPatternUtilTest { @Test public void matchedDatesTest() { //测试每30秒执行 - final List matchedDates = CronPatternUtil.matchedDates("0/30 * 8-18 * * ?", DateUtil.parse("2018-10-15 14:33:22"), 5, true); + final List matchedDates = CronPatternUtil.matchedDates("0/30 * 8-18 * * ?", DateUtil.parse("2018-10-15 14:33:22"), 5); Assertions.assertEquals(5, matchedDates.size()); Assertions.assertEquals("2018-10-15 14:33:30", matchedDates.get(0).toString()); Assertions.assertEquals("2018-10-15 14:34:00", matchedDates.get(1).toString()); @@ -36,7 +36,7 @@ public class CronPatternUtilTest { @Test public void matchedDatesTest2() { //测试每小时执行 - final List matchedDates = CronPatternUtil.matchedDates("0 0 */1 * * *", DateUtil.parse("2018-10-15 14:33:22"), 5, true); + final List matchedDates = CronPatternUtil.matchedDates("0 0 */1 * * *", DateUtil.parse("2018-10-15 14:33:22"), 5); Assertions.assertEquals(5, matchedDates.size()); Assertions.assertEquals("2018-10-15 15:00:00", matchedDates.get(0).toString()); Assertions.assertEquals("2018-10-15 16:00:00", matchedDates.get(1).toString()); @@ -48,7 +48,7 @@ public class CronPatternUtilTest { @Test public void matchedDatesTest3() { //测试最后一天 - final List matchedDates = CronPatternUtil.matchedDates("0 0 */1 L * *", DateUtil.parse("2018-10-30 23:33:22"), 5, true); + final List matchedDates = CronPatternUtil.matchedDates("0 0 */1 L * *", DateUtil.parse("2018-10-30 23:33:22"), 5); Assertions.assertEquals(5, matchedDates.size()); Assertions.assertEquals("2018-10-31 00:00:00", matchedDates.get(0).toString()); Assertions.assertEquals("2018-10-31 01:00:00", matchedDates.get(1).toString()); diff --git a/hutool-cron/src/test/java/org/dromara/hutool/cron/pattern/IssueI82CSHTest.java b/hutool-cron/src/test/java/org/dromara/hutool/cron/pattern/IssueI82CSHTest.java index 7b8adf13a..7ab82846c 100644 --- a/hutool-cron/src/test/java/org/dromara/hutool/cron/pattern/IssueI82CSHTest.java +++ b/hutool-cron/src/test/java/org/dromara/hutool/cron/pattern/IssueI82CSHTest.java @@ -27,7 +27,7 @@ public class IssueI82CSHTest { void test() { final DateTime begin = DateUtil.parse("2023-09-20"); final DateTime end = DateUtil.parse("2025-09-20"); - final List dates = CronPatternUtil.matchedDates("0 0 1 3-3,9 *", begin, end, 20, false); + final List dates = CronPatternUtil.matchedDates("0 0 1 3-3,9 *", begin, end, 20); //dates.forEach(Console::log); Assertions.assertEquals(4, dates.size()); } diff --git a/hutool-cron/src/test/java/org/dromara/hutool/cron/pattern/IssueI9FQUATest.java b/hutool-cron/src/test/java/org/dromara/hutool/cron/pattern/IssueI9FQUATest.java new file mode 100644 index 000000000..081af5e34 --- /dev/null +++ b/hutool-cron/src/test/java/org/dromara/hutool/cron/pattern/IssueI9FQUATest.java @@ -0,0 +1,78 @@ +/* + * 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.dromara.hutool.core.date.StopWatch; +import org.dromara.hutool.core.lang.Console; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.quartz.CronExpression; + +import java.text.ParseException; +import java.util.Calendar; + +public class IssueI9FQUATest { + + @Test + void nextDateAfterTest() { + final String cron = "0/5 * * * * ?"; + final Calendar calendar = CronPattern.of(cron).nextMatchAfter( + DateUtil.parse("2024-01-01 00:00:00").toCalendar()); + + //Console.log(DateUtil.date(calendar)); + Assertions.assertEquals("2024-01-01 00:00:05", DateUtil.date(calendar).toString()); + } + + @Test + @Disabled + void createPatternBatchTest() throws ParseException { + final String cron = "0/5 * * * * ?"; + final StopWatch stopWatch = new StopWatch(); + + stopWatch.start("Hutool"); + CronPattern.of(cron); + stopWatch.stop(); + + stopWatch.start("Quartz"); + new CronExpression(cron); + stopWatch.stop(); + + Console.log(stopWatch.prettyPrint()); + } + + @Test + @Disabled + void nextDateAfterBatchTest() throws ParseException { + final String cron = "0/5 * * * * ?"; + final DateTime date = DateUtil.parse("2024-04-11 11:31:30"); + final Calendar calendar = date.toCalendar(); + final CronPattern cronPattern = CronPattern.of(cron); + final CronExpression expression = new CronExpression(cron); + + + final StopWatch stopWatch = new StopWatch(); + + stopWatch.start("Hutool"); + cronPattern.nextMatchAfter(calendar); + stopWatch.stop(); + + stopWatch.start("Quartz"); + expression.getNextValidTimeAfter(date); + stopWatch.stop(); + + Console.log(stopWatch.prettyPrint()); + } +}