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());
+ }
+}