mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-07-21 15:09:48 +08:00
修复CronPattern.nextMatchAfter匹配初始值问题
This commit is contained in:
@@ -43,5 +43,11 @@
|
|||||||
<artifactId>hutool-setting</artifactId>
|
<artifactId>hutool-setting</artifactId>
|
||||||
<version>${project.parent.version}</version>
|
<version>${project.parent.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.quartz-scheduler</groupId>
|
||||||
|
<artifactId>quartz</artifactId>
|
||||||
|
<version>2.3.2</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
package org.dromara.hutool.cron.pattern;
|
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.core.date.CalendarUtil;
|
||||||
import org.dromara.hutool.cron.pattern.matcher.PatternMatcher;
|
import org.dromara.hutool.cron.pattern.matcher.PatternMatcher;
|
||||||
import org.dromara.hutool.cron.pattern.parser.PatternParser;
|
import org.dromara.hutool.cron.pattern.parser.PatternParser;
|
||||||
@@ -156,7 +156,14 @@ public class CronPattern {
|
|||||||
* @param calendar 时间
|
* @param calendar 时间
|
||||||
* @return 匹配到的下一个时间
|
* @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());
|
Calendar next = nextMatchAfter(PatternUtil.getFields(calendar, true), calendar.getTimeZone());
|
||||||
if (!match(next, true)) {
|
if (!match(next, true)) {
|
||||||
next.set(Calendar.DAY_OF_MONTH, next.get(Calendar.DAY_OF_MONTH) + 1);
|
next.set(Calendar.DAY_OF_MONTH, next.get(Calendar.DAY_OF_MONTH) + 1);
|
||||||
@@ -211,11 +218,15 @@ public class CronPattern {
|
|||||||
* @return {@link Calendar},毫秒数为0
|
* @return {@link Calendar},毫秒数为0
|
||||||
*/
|
*/
|
||||||
private Calendar nextMatchAfter(final int[] values, final TimeZone zone) {
|
private Calendar nextMatchAfter(final int[] values, final TimeZone zone) {
|
||||||
final List<Calendar> nextMatches = new ArrayList<>(matchers.size());
|
Calendar minMatch = null;
|
||||||
for (final PatternMatcher matcher : matchers) {
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -12,12 +12,12 @@
|
|||||||
|
|
||||||
package org.dromara.hutool.cron.pattern;
|
package org.dromara.hutool.cron.pattern;
|
||||||
|
|
||||||
import org.dromara.hutool.core.collection.CollUtil;
|
import org.dromara.hutool.core.date.CalendarUtil;
|
||||||
import org.dromara.hutool.core.date.DateUnit;
|
|
||||||
import org.dromara.hutool.core.date.DateUtil;
|
import org.dromara.hutool.core.date.DateUtil;
|
||||||
import org.dromara.hutool.core.lang.Assert;
|
import org.dromara.hutool.core.lang.Assert;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Calendar;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -34,16 +34,11 @@ public class CronPatternUtil {
|
|||||||
*
|
*
|
||||||
* @param pattern 表达式
|
* @param pattern 表达式
|
||||||
* @param start 起始时间
|
* @param start 起始时间
|
||||||
* @param isMatchSecond 是否匹配秒
|
|
||||||
* @return 日期
|
* @return 日期
|
||||||
* @since 4.5.8
|
* @since 4.5.8
|
||||||
*/
|
*/
|
||||||
public static Date nextDateAfter(final CronPattern pattern, final Date start, final boolean isMatchSecond) {
|
public static Date nextDateAfter(final CronPattern pattern, final Date start) {
|
||||||
final List<Date> matchedDates = matchedDates(pattern, start.getTime(), DateUtil.endOfYear(start).getTime(), 1, isMatchSecond);
|
return DateUtil.date(pattern.nextMatchAfter(CalendarUtil.calendar(start)));
|
||||||
if (CollUtil.isNotEmpty(matchedDates)) {
|
|
||||||
return matchedDates.get(0);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,11 +47,10 @@ public class CronPatternUtil {
|
|||||||
* @param patternStr 表达式字符串
|
* @param patternStr 表达式字符串
|
||||||
* @param start 起始时间
|
* @param start 起始时间
|
||||||
* @param count 列举数量
|
* @param count 列举数量
|
||||||
* @param isMatchSecond 是否匹配秒
|
|
||||||
* @return 日期列表
|
* @return 日期列表
|
||||||
*/
|
*/
|
||||||
public static List<Date> matchedDates(final String patternStr, final Date start, final int count, final boolean isMatchSecond) {
|
public static List<Date> matchedDates(final String patternStr, final Date start, final int count) {
|
||||||
return matchedDates(patternStr, start, DateUtil.endOfYear(start), count, isMatchSecond);
|
return matchedDates(patternStr, start, DateUtil.endOfYear(start), count);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,11 +60,10 @@ public class CronPatternUtil {
|
|||||||
* @param start 起始时间
|
* @param start 起始时间
|
||||||
* @param end 结束时间
|
* @param end 结束时间
|
||||||
* @param count 列举数量
|
* @param count 列举数量
|
||||||
* @param isMatchSecond 是否匹配秒
|
|
||||||
* @return 日期列表
|
* @return 日期列表
|
||||||
*/
|
*/
|
||||||
public static List<Date> matchedDates(final String patternStr, final Date start, final Date end, final int count, final boolean isMatchSecond) {
|
public static List<Date> matchedDates(final String patternStr, final Date start, final Date end, final int count) {
|
||||||
return matchedDates(patternStr, start.getTime(), end.getTime(), count, isMatchSecond);
|
return matchedDates(patternStr, start.getTime(), end.getTime(), count);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -80,11 +73,10 @@ public class CronPatternUtil {
|
|||||||
* @param start 起始时间
|
* @param start 起始时间
|
||||||
* @param end 结束时间
|
* @param end 结束时间
|
||||||
* @param count 列举数量
|
* @param count 列举数量
|
||||||
* @param isMatchSecond 是否匹配秒
|
|
||||||
* @return 日期列表
|
* @return 日期列表
|
||||||
*/
|
*/
|
||||||
public static List<Date> matchedDates(final String patternStr, final long start, final long end, final int count, final boolean isMatchSecond) {
|
public static List<Date> matchedDates(final String patternStr, final long start, final long end, final int count) {
|
||||||
return matchedDates(new CronPattern(patternStr), start, end, count, isMatchSecond);
|
return matchedDates(new CronPattern(patternStr), start, end, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -94,22 +86,22 @@ public class CronPatternUtil {
|
|||||||
* @param start 起始时间
|
* @param start 起始时间
|
||||||
* @param end 结束时间
|
* @param end 结束时间
|
||||||
* @param count 列举数量
|
* @param count 列举数量
|
||||||
* @param isMatchSecond 是否匹配秒
|
|
||||||
* @return 日期列表
|
* @return 日期列表
|
||||||
*/
|
*/
|
||||||
public static List<Date> matchedDates(final CronPattern pattern, final long start, final long end, final int count, final boolean isMatchSecond) {
|
public static List<Date> matchedDates(final CronPattern pattern, final long start, final long end, final int count) {
|
||||||
Assert.isTrue(start < end, "Start date is later than end !");
|
Assert.isTrue(start < end, "Start date is later than end !");
|
||||||
|
|
||||||
final List<Date> result = new ArrayList<>(count);
|
final List<Date> result = new ArrayList<>(count);
|
||||||
final long step = isMatchSecond ? DateUnit.SECOND.getMillis() : DateUnit.MINUTE.getMillis();
|
|
||||||
for (long i = start; i < end; i += step) {
|
Calendar calendar = pattern.nextMatchAfter(CalendarUtil.calendar(start));
|
||||||
if (pattern.match(i, isMatchSecond)) {
|
while(calendar.getTimeInMillis() < end){
|
||||||
result.add(DateUtil.date(i));
|
result.add(DateUtil.date(calendar));
|
||||||
if (result.size() >= count) {
|
if(result.size() >= count){
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
calendar = pattern.nextMatchAfter(calendar);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -150,7 +150,8 @@ public class PatternMatcher {
|
|||||||
* 下 <-----------------> 上
|
* 下 <-----------------> 上
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* @param values 时间字段值,{second, minute, hour, dayOfMonth, monthBase1, dayOfWeekBase0, year}
|
* @param values 时间字段值,{second, minute, hour, dayOfMonth, monthBase1, dayOfWeekBase0, year},
|
||||||
|
* 注意这个字段值会被修改
|
||||||
* @param zone 时区
|
* @param zone 时区
|
||||||
* @return {@link Calendar},毫秒数为0
|
* @return {@link Calendar},毫秒数为0
|
||||||
*/
|
*/
|
||||||
@@ -187,8 +188,6 @@ public class PatternMatcher {
|
|||||||
* @return {@link Calendar},毫秒数为0
|
* @return {@link Calendar},毫秒数为0
|
||||||
*/
|
*/
|
||||||
private int[] nextMatchValuesAfter(final int[] values) {
|
private int[] nextMatchValuesAfter(final int[] values) {
|
||||||
final int[] newValues = values.clone();
|
|
||||||
|
|
||||||
int i = Part.YEAR.ordinal();
|
int i = Part.YEAR.ordinal();
|
||||||
// 新值,-1表示标识为回退
|
// 新值,-1表示标识为回退
|
||||||
int nextValue = 0;
|
int nextValue = 0;
|
||||||
@@ -199,11 +198,11 @@ public class PatternMatcher {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
nextValue = getNextMatch(newValues, i, 0);
|
nextValue = getNextMatch(values, i, 0);
|
||||||
|
|
||||||
if (nextValue > values[i]) {
|
if (nextValue > values[i]) {
|
||||||
// 此部分正常获取新值,结束循环,后续的部分置最小值
|
// 此部分正常获取新值,结束循环,后续的部分置最小值
|
||||||
newValues[i] = nextValue;
|
values[i] = nextValue;
|
||||||
i--;
|
i--;
|
||||||
break;
|
break;
|
||||||
} else if (nextValue < values[i]) {
|
} else if (nextValue < values[i]) {
|
||||||
@@ -226,10 +225,10 @@ public class PatternMatcher {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
nextValue = getNextMatch(newValues, i, 1);
|
nextValue = getNextMatch(values, i, 1);
|
||||||
|
|
||||||
if (nextValue > values[i]) {
|
if (nextValue > values[i]) {
|
||||||
newValues[i] = nextValue;
|
values[i] = nextValue;
|
||||||
i--;
|
i--;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -238,8 +237,8 @@ public class PatternMatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 修改值以下的字段全部归最小值
|
// 修改值以下的字段全部归最小值
|
||||||
setToMin(newValues, i);
|
setToMin(values, i);
|
||||||
return newValues;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -28,7 +28,7 @@ public class CronPatternNextMatchTest {
|
|||||||
CronPattern pattern = new CronPattern("* * * * * * *");
|
CronPattern pattern = new CronPattern("* * * * * * *");
|
||||||
DateTime date = DateUtil.truncate(DateUtil.now(), DateField.SECOND);
|
DateTime date = DateUtil.truncate(DateUtil.now(), DateField.SECOND);
|
||||||
Calendar calendar = pattern.nextMatchAfter(date.toCalendar());
|
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 * * * * * *");
|
pattern = new CronPattern("0 * * * * * *");
|
||||||
|
@@ -24,7 +24,7 @@ public class CronPatternUtilTest {
|
|||||||
@Test
|
@Test
|
||||||
public void matchedDatesTest() {
|
public void matchedDatesTest() {
|
||||||
//测试每30秒执行
|
//测试每30秒执行
|
||||||
final List<Date> matchedDates = CronPatternUtil.matchedDates("0/30 * 8-18 * * ?", DateUtil.parse("2018-10-15 14:33:22"), 5, true);
|
final List<Date> matchedDates = CronPatternUtil.matchedDates("0/30 * 8-18 * * ?", DateUtil.parse("2018-10-15 14:33:22"), 5);
|
||||||
Assertions.assertEquals(5, matchedDates.size());
|
Assertions.assertEquals(5, matchedDates.size());
|
||||||
Assertions.assertEquals("2018-10-15 14:33:30", matchedDates.get(0).toString());
|
Assertions.assertEquals("2018-10-15 14:33:30", matchedDates.get(0).toString());
|
||||||
Assertions.assertEquals("2018-10-15 14:34:00", matchedDates.get(1).toString());
|
Assertions.assertEquals("2018-10-15 14:34:00", matchedDates.get(1).toString());
|
||||||
@@ -36,7 +36,7 @@ public class CronPatternUtilTest {
|
|||||||
@Test
|
@Test
|
||||||
public void matchedDatesTest2() {
|
public void matchedDatesTest2() {
|
||||||
//测试每小时执行
|
//测试每小时执行
|
||||||
final List<Date> matchedDates = CronPatternUtil.matchedDates("0 0 */1 * * *", DateUtil.parse("2018-10-15 14:33:22"), 5, true);
|
final List<Date> matchedDates = CronPatternUtil.matchedDates("0 0 */1 * * *", DateUtil.parse("2018-10-15 14:33:22"), 5);
|
||||||
Assertions.assertEquals(5, matchedDates.size());
|
Assertions.assertEquals(5, matchedDates.size());
|
||||||
Assertions.assertEquals("2018-10-15 15:00:00", matchedDates.get(0).toString());
|
Assertions.assertEquals("2018-10-15 15:00:00", matchedDates.get(0).toString());
|
||||||
Assertions.assertEquals("2018-10-15 16:00:00", matchedDates.get(1).toString());
|
Assertions.assertEquals("2018-10-15 16:00:00", matchedDates.get(1).toString());
|
||||||
@@ -48,7 +48,7 @@ public class CronPatternUtilTest {
|
|||||||
@Test
|
@Test
|
||||||
public void matchedDatesTest3() {
|
public void matchedDatesTest3() {
|
||||||
//测试最后一天
|
//测试最后一天
|
||||||
final List<Date> matchedDates = CronPatternUtil.matchedDates("0 0 */1 L * *", DateUtil.parse("2018-10-30 23:33:22"), 5, true);
|
final List<Date> matchedDates = CronPatternUtil.matchedDates("0 0 */1 L * *", DateUtil.parse("2018-10-30 23:33:22"), 5);
|
||||||
Assertions.assertEquals(5, matchedDates.size());
|
Assertions.assertEquals(5, matchedDates.size());
|
||||||
Assertions.assertEquals("2018-10-31 00:00:00", matchedDates.get(0).toString());
|
Assertions.assertEquals("2018-10-31 00:00:00", matchedDates.get(0).toString());
|
||||||
Assertions.assertEquals("2018-10-31 01:00:00", matchedDates.get(1).toString());
|
Assertions.assertEquals("2018-10-31 01:00:00", matchedDates.get(1).toString());
|
||||||
|
@@ -27,7 +27,7 @@ public class IssueI82CSHTest {
|
|||||||
void test() {
|
void test() {
|
||||||
final DateTime begin = DateUtil.parse("2023-09-20");
|
final DateTime begin = DateUtil.parse("2023-09-20");
|
||||||
final DateTime end = DateUtil.parse("2025-09-20");
|
final DateTime end = DateUtil.parse("2025-09-20");
|
||||||
final List<Date> dates = CronPatternUtil.matchedDates("0 0 1 3-3,9 *", begin, end, 20, false);
|
final List<Date> dates = CronPatternUtil.matchedDates("0 0 1 3-3,9 *", begin, end, 20);
|
||||||
//dates.forEach(Console::log);
|
//dates.forEach(Console::log);
|
||||||
Assertions.assertEquals(4, dates.size());
|
Assertions.assertEquals(4, dates.size());
|
||||||
}
|
}
|
||||||
|
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user