This commit is contained in:
Looly 2022-03-28 03:42:05 +08:00
parent 6427d6fb13
commit 2dde8d18d0
4 changed files with 147 additions and 95 deletions

View File

@ -151,7 +151,7 @@ public class CronPattern {
final int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) - 1; // 星期从0开始0和7都表示周日
final int year = calendar.get(Calendar.YEAR);
return nextMatchAfter(second, minute, hour, dayOfMonth, month, dayOfWeek, year, calendar.getTimeZone());
return nextMatchAfter(new int[]{second, minute, hour, dayOfMonth, month, dayOfWeek, year}, calendar.getTimeZone());
}
@Override
@ -183,23 +183,16 @@ public class CronPattern {
/**
* 获取下一个最近的匹配日期时间
*
* @param second
* @param minute
* @param hour
* @param dayOfMonth
* @param month 从1开始
* @param dayOfWeek 从0开始, 0表示周日
* @param year
* @param values 时间字段值
* @param zone 时区
* @return {@link Calendar}
*/
private Calendar nextMatchAfter(int second, int minute, int hour, int dayOfMonth, int month, int dayOfWeek, int year, TimeZone zone) {
List<Calendar> nextMatchs = new ArrayList<>(second);
private Calendar nextMatchAfter(int[] values, TimeZone zone) {
final List<Calendar> nextMatches = new ArrayList<>(matchers.size());
for (PatternMatcher matcher : matchers) {
nextMatchs.add(matcher.nextMatchAfter(
second, minute, hour, dayOfMonth, month, dayOfWeek, year, zone));
nextMatches.add(matcher.nextMatchAfter(values, zone));
}
// 返回匹配到的最早日期
return CollUtil.min(nextMatchs);
return CollUtil.min(nextMatches);
}
}

View File

@ -5,6 +5,8 @@ import cn.hutool.core.date.Week;
import cn.hutool.core.lang.Assert;
import cn.hutool.cron.CronException;
import java.util.Calendar;
/**
* 表达式各个部分的枚举用于限定在表达式中的位置和规则如最小值和最大值<br>
* {@link #ordinal()}表示此部分在表达式中的位置如0表示秒<br>
@ -18,24 +20,30 @@ import cn.hutool.cron.CronException;
* @since 5.8.0
*/
public enum Part {
SECOND(0, 59),
MINUTE(0, 59),
HOUR(0, 23),
DAY_OF_MONTH(1, 31),
MONTH(Month.JANUARY.getValueBaseOne(), Month.DECEMBER.getValueBaseOne()),
DAY_OF_WEEK(Week.SUNDAY.ordinal(), Week.SATURDAY.ordinal()),
YEAR(1970, 2099);
SECOND(Calendar.SECOND, 0, 59),
MINUTE(Calendar.MINUTE, 0, 59),
HOUR(Calendar.HOUR_OF_DAY, 0, 23),
DAY_OF_MONTH(Calendar.DAY_OF_MONTH, 1, 31),
MONTH(Calendar.MONTH, Month.JANUARY.getValueBaseOne(), Month.DECEMBER.getValueBaseOne()),
DAY_OF_WEEK(Calendar.DAY_OF_WEEK, Week.SUNDAY.ordinal(), Week.SATURDAY.ordinal()),
YEAR(Calendar.YEAR, 1970, 2099);
// ---------------------------------------------------------------
private static final Part[] ENUMS = Part.values();
private final int calendarField;
private final int min;
private final int max;
/**
* 构造
*
* @param min 限定最小值包含
* @param max 限定最大值包含
* @param calendarField Calendar中对应字段项
* @param min 限定最小值包含
* @param max 限定最大值包含
*/
Part(int min, int max) {
Part(int calendarField, int min, int max) {
this.calendarField = calendarField;
if (min > max) {
this.min = max;
this.max = min;
@ -45,6 +53,15 @@ public enum Part {
}
}
/**
* 获取Calendar中对应字段项
*
* @return Calendar中对应字段项
*/
public int getCalendarField() {
return this.calendarField;
}
/**
* 获取最小值
*
@ -75,4 +92,14 @@ public enum Part {
() -> new CronException("Value {} out of range: [{} , {}]", value, min, max));
return value;
}
/**
* 根据位置获取Part
*
* @param i 位置从0开始
* @return Part
*/
public static Part of(int i) {
return ENUMS[i];
}
}

View File

@ -1,6 +1,5 @@
package cn.hutool.cron.pattern.matcher;
import cn.hutool.core.lang.mutable.MutableBool;
import cn.hutool.cron.pattern.Part;
import java.time.Year;
@ -106,79 +105,108 @@ public class PatternMatcher {
/**
* 获取下一个匹配日期时间
*
* @param second
* @param minute
* @param hour
* @param dayOfMonth
* @param month 从1开始
* @param dayOfWeek 从0开始, 0表示周日
* @param year
* @param zone 时区
* @param values 时间字段值
* @param zone 时区
* @return {@link Calendar}
*/
public Calendar nextMatchAfter(int second, int minute, int hour,
int dayOfMonth, int month, int dayOfWeek, int year, TimeZone zone) {
public Calendar nextMatchAfter(int[] values, TimeZone zone) {
Calendar calendar = Calendar.getInstance(zone);
// 上一个字段不一致说明产生了新值下一个字段使用最小值
MutableBool isNextEquals = new MutableBool(true);
//
final int nextYear = nextAfter(get(Part.YEAR), year, isNextEquals);
calendar.set(Calendar.YEAR, nextYear);
int i = Part.YEAR.ordinal();
int nextValue = 0;
while (i >= 0) {
nextValue = matchers[i].nextAfter(values[i]);
if (nextValue > values[i]) {
// 此部分正常获取新值结束循环后续的部分置最小值
setValue(calendar, Part.of(i), nextValue);
i--;
break;
} else if (nextValue < values[i]) {
// 此部分下一个值获取到的值产生回退回到上一个部分继续获取新值
i++;
nextValue = -1;// 标记回退查找
break;
}
// 值不变设置后检查下一个部分
setValue(calendar, Part.of(i), nextValue);
i--;
}
//
final int nextDayOfWeek = nextAfter(get(Part.DAY_OF_WEEK), dayOfWeek, isNextEquals);
calendar.set(Calendar.DAY_OF_WEEK, nextDayOfWeek + 1);
// 值产生回退向上查找变更值
if(-1 == nextValue){
while(i <= Part.YEAR.ordinal()){
nextValue = matchers[i].nextAfter(values[i] + 1);
if(nextValue > values[i]){
setValue(calendar, Part.of(i), nextValue);
i--;
break;
}
i++;
}
}
//
final int nextMonth = nextAfter(get(Part.MONTH), month, isNextEquals);
calendar.set(Calendar.MONTH, nextMonth - 1);
//
final int nextDayOfMonth = nextAfter(get(Part.DAY_OF_MONTH), dayOfMonth, isNextEquals);
calendar.set(Calendar.DAY_OF_MONTH, nextDayOfMonth);
//
final int nextHour = nextAfter(get(Part.HOUR), hour, isNextEquals);
calendar.set(Calendar.HOUR_OF_DAY, nextHour);
//
int nextMinute = nextAfter(get(Part.MINUTE), minute, isNextEquals);
calendar.set(Calendar.MINUTE, nextMinute);
//
final int nextSecond = nextAfter(get(Part.SECOND), second, isNextEquals);
calendar.set(Calendar.SECOND, nextSecond);
// 修改值以下的字段全部归最小值
setToMin(calendar, i);
return calendar;
}
/**
* 获取对应字段匹配器的下一个
* 设置从{@link Part#SECOND}到指定部分全部设置为最小值
*
* @param matcher 匹配器
* @param value
* @param isNextEquals 是否下一个值和值相同不同获取初始值相同获取下一值然后修改
* @return 下一个值-1标识匹配所有值的情况应获取整个字段的最小值
* @param calendar {@link Calendar}
* @param toPart 截止的部分
* @return {@link Calendar}
*/
private static int nextAfter(PartMatcher matcher, int value, MutableBool isNextEquals) {
int nextValue;
if (isNextEquals.get()) {
// 上一层级得到相同值下级获取下个值
nextValue = matcher.nextAfter(value);
isNextEquals.set(nextValue == value);
} else {
// 上一层级的值得到了不同值下级的所有值使用最小值
if (matcher instanceof AlwaysTrueMatcher) {
nextValue = value;
} else if (matcher instanceof BoolArrayMatcher) {
nextValue = ((BoolArrayMatcher) matcher).getMinValue();
} else {
throw new IllegalArgumentException("Invalid matcher: " + matcher.getClass().getName());
}
private Calendar setToMin(Calendar calendar, int toPart) {
Part part;
for (int i = 0; i <= toPart; i++) {
part = Part.of(i);
setValue(calendar, part, getMin(part));
}
return nextValue;
return calendar;
}
/**
* 获取表达式部分的最小值
*
* @param part {@link Part}
* @return 最小值如果匹配所有返回对应部分范围的最小值
*/
private int getMin(Part part) {
PartMatcher matcher = get(part);
int min;
if (matcher instanceof AlwaysTrueMatcher) {
min = part.getMin();
} else if (matcher instanceof BoolArrayMatcher) {
min = ((BoolArrayMatcher) matcher).getMinValue();
} else {
throw new IllegalArgumentException("Invalid matcher: " + matcher.getClass().getName());
}
return min;
}
//endregion
/**
* 设置对应部分修正后的值
* @param calendar {@link Calendar}
* @param part 表达式部分
* @param value
* @return {@link Calendar}
*/
private Calendar setValue(Calendar calendar, Part part, int value){
switch (part){
case MONTH:
value -= 1;
break;
case DAY_OF_WEEK:
value += 1;
break;
}
//noinspection MagicConstant
calendar.set(part.getCalendarField(), value);
return calendar;
}
}

View File

@ -1,10 +1,7 @@
package cn.hutool.cron.pattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.Week;
import cn.hutool.core.lang.Console;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import java.util.Calendar;
@ -14,20 +11,27 @@ public class CronPatternNextMatchTest {
@Test
public void nextMatchAfterTest(){
CronPattern pattern = new CronPattern("23 12 * 12 * * *");
// 时间正常递增
//noinspection ConstantConditions
final Calendar calendar = pattern.nextMatchAfter(
DateUtil.parse("2022-04-12 09:12:24").toCalendar());
Calendar calendar = pattern.nextMatchAfter(
DateUtil.parse("2022-04-12 09:12:12").toCalendar());
Console.log(DateUtil.date(calendar));
Assert.assertTrue(pattern.match(calendar, true));
}
Assert.assertEquals("2022-04-12 09:12:23", DateUtil.date(calendar).toString());
@Test
@Ignore
public void calendarTest(){
final Calendar ca = Calendar.getInstance();
ca.set(Calendar.DAY_OF_WEEK, Week.SATURDAY.getValue());
// 秒超出规定值的最大值小时+1
//noinspection ConstantConditions
calendar = pattern.nextMatchAfter(
DateUtil.parse("2022-04-12 09:12:24").toCalendar());
Assert.assertTrue(pattern.match(calendar, true));
Assert.assertEquals("2022-04-12 10:12:23", DateUtil.date(calendar).toString());
Console.log(DateUtil.date(ca));
// 天超出规定值的最大值+1
//noinspection ConstantConditions
calendar = pattern.nextMatchAfter(
DateUtil.parse("2022-04-13 09:12:24").toCalendar());
Assert.assertTrue(pattern.match(calendar, true));
Assert.assertEquals("2022-05-12 00:12:23", DateUtil.date(calendar).toString());
}
}