This commit is contained in:
Looly 2022-03-27 20:01:02 +08:00
parent c2e1bbafc8
commit 5966dcc626
13 changed files with 300 additions and 326 deletions

View File

@ -1,10 +1,13 @@
package cn.hutool.cron.pattern;
import cn.hutool.cron.pattern.matcher.MatcherTable;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.cron.pattern.matcher.PatternMatcher;
import cn.hutool.cron.pattern.parser.PatternParser;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.TimeZone;
/**
@ -66,7 +69,7 @@ import java.util.TimeZone;
public class CronPattern {
private final String pattern;
private final MatcherTable matcherTable;
private final List<PatternMatcher> matchers;
/**
* 解析表达式为 CronPattern
@ -86,11 +89,9 @@ public class CronPattern {
*/
public CronPattern(String pattern) {
this.pattern = pattern;
this.matcherTable = PatternParser.parse(pattern);
this.matchers = PatternParser.parse(pattern);
}
// --------------------------------------------------------------------------------------- match start
/**
* 给定时间是否匹配定时任务表达式
*
@ -116,24 +117,6 @@ public class CronPattern {
return match(calendar, isMatchSecond);
}
/**
* 返回匹配到的下一个时间
*
* @param calendar 时间
* @return 匹配到的下一个时间
*/
public Calendar nextMatchAfter(Calendar calendar) {
final int second = calendar.get(Calendar.SECOND);
final int minute = calendar.get(Calendar.MINUTE);
final int hour = calendar.get(Calendar.HOUR_OF_DAY);
final int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
final int month = calendar.get(Calendar.MONTH) + 1;// 月份从1开始
final int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) - 1; // 星期从0开始0和7都表示周日
final int year = calendar.get(Calendar.YEAR);
return this.matcherTable.nextMatchAfter(second, minute, hour, dayOfMonth, month, dayOfWeek, year, calendar.getTimeZone());
}
/**
* 给定时间是否匹配定时任务表达式
*
@ -150,12 +133,73 @@ public class CronPattern {
final int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) - 1; // 星期从0开始0和7都表示周日
final int year = calendar.get(Calendar.YEAR);
return this.matcherTable.match(second, minute, hour, dayOfMonth, month, dayOfWeek, year);
return match(second, minute, hour, dayOfMonth, month, dayOfWeek, year);
}
/**
* 返回匹配到的下一个时间
*
* @param calendar 时间
* @return 匹配到的下一个时间
*/
public Calendar nextMatchAfter(Calendar calendar) {
final int second = calendar.get(Calendar.SECOND);
final int minute = calendar.get(Calendar.MINUTE);
final int hour = calendar.get(Calendar.HOUR_OF_DAY);
final int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
final int month = calendar.get(Calendar.MONTH) + 1;// 月份从1开始
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());
}
// --------------------------------------------------------------------------------------- match end
@Override
public String toString() {
return this.pattern;
}
/**
* 给定时间是否匹配定时任务表达式
*
* @param second 秒数-1表示不匹配此项
* @param minute 分钟
* @param hour 小时
* @param dayOfMonth
* @param month 从1开始
* @param dayOfWeek 从0开始0和7都表示周日
* @param year
* @return 如果匹配返回 {@code true}, 否则返回 {@code false}
*/
private boolean match(int second, int minute, int hour, int dayOfMonth, int month, int dayOfWeek, int year) {
for (PatternMatcher matcher : matchers) {
if (matcher.match(second, minute, hour, dayOfMonth, month, dayOfWeek, year)) {
return true;
}
}
return false;
}
/**
* 获取下一个最近的匹配日期时间
*
* @param second
* @param minute
* @param hour
* @param dayOfMonth
* @param month 从1开始
* @param dayOfWeek 从0开始, 0表示周日
* @param year
* @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);
for (PatternMatcher matcher : matchers) {
nextMatchs.add(matcher.nextMatchAfter(
second, minute, hour, dayOfMonth, month, dayOfWeek, year, zone));
}
// 返回匹配到的最早日期
return CollUtil.min(nextMatchs);
}
}

View File

@ -7,9 +7,9 @@ import cn.hutool.core.util.StrUtil;
*
* @author Looly
*/
public class AlwaysTrueValueMatcher implements ValueMatcher {
public class AlwaysTrueMatcher implements PartMatcher {
public static AlwaysTrueValueMatcher INSTANCE = new AlwaysTrueValueMatcher();
public static AlwaysTrueMatcher INSTANCE = new AlwaysTrueMatcher();
@Override
public boolean match(Integer t) {

View File

@ -12,7 +12,7 @@ import java.util.List;
*
* @author Looly
*/
public class BoolArrayValueMatcher implements ValueMatcher {
public class BoolArrayMatcher implements PartMatcher {
/**
* 用户定义此字段的最小值
@ -25,7 +25,7 @@ public class BoolArrayValueMatcher implements ValueMatcher {
*
* @param intValueList 匹配值列表
*/
public BoolArrayValueMatcher(List<Integer> intValueList) {
public BoolArrayMatcher(List<Integer> intValueList) {
Assert.isTrue(CollUtil.isNotEmpty(intValueList), "Values must be not empty!");
bValues = new boolean[Collections.max(intValueList) + 1];
int min = Integer.MAX_VALUE;

View File

@ -1,95 +0,0 @@
package cn.hutool.cron.pattern.matcher;
import java.time.Year;
/**
* 日期和时间的单一匹配器
*
* @author looly
* @since 5.8.0
*/
public class DateTimeMatcher {
/**
* 秒字段匹配列表
*/
final ValueMatcher secondMatcher;
/**
* 分字段匹配列表
*/
final ValueMatcher minuteMatcher;
/**
* 时字段匹配列表
*/
final ValueMatcher hourMatcher;
/**
* 每月几号字段匹配列表
*/
final ValueMatcher dayOfMonthMatcher;
/**
* 月字段匹配列表
*/
final ValueMatcher monthMatcher;
/**
* 星期字段匹配列表
*/
final ValueMatcher dayOfWeekMatcher;
/**
* 年字段匹配列表
*/
final ValueMatcher yearMatcher;
public DateTimeMatcher(ValueMatcher secondMatcher,
ValueMatcher minuteMatcher,
ValueMatcher hourMatchers,
ValueMatcher dayOfMonthMatchers,
ValueMatcher monthMatchers,
ValueMatcher dayOfWeekMatchers,
ValueMatcher yearMatchers) {
this.secondMatcher = secondMatcher;
this.minuteMatcher = minuteMatcher;
this.hourMatcher = hourMatchers;
this.dayOfMonthMatcher = dayOfMonthMatchers;
this.monthMatcher = monthMatchers;
this.dayOfWeekMatcher = dayOfWeekMatchers;
this.yearMatcher = yearMatchers;
}
/**
* 给定时间是否匹配定时任务表达式
*
* @param second 秒数-1表示不匹配此项
* @param minute 分钟
* @param hour 小时
* @param dayOfMonth
* @param month 从1开始
* @param dayOfWeek 从0开始0和7都表示周日
* @param year
* @return 如果匹配返回 {@code true}, 否则返回 {@code false}
*/
public boolean match(int second, int minute, int hour, int dayOfMonth, int month, int dayOfWeek, int year) {
return ((second < 0) || secondMatcher.match(second)) // 匹配秒非秒匹配模式下始终返回true
&& minuteMatcher.match(minute)// 匹配分
&& hourMatcher.match(hour)// 匹配时
&& isMatchDayOfMonth(dayOfMonthMatcher, dayOfMonth, month, Year.isLeap(year))// 匹配日
&& monthMatcher.match(month) // 匹配月
&& dayOfWeekMatcher.match(dayOfWeek)// 匹配周
&& yearMatcher.match(year);// 匹配年
}
/**
* 是否匹配日指定月份的第几天
*
* @param matcher {@link ValueMatcher}
* @param dayOfMonth
* @param month
* @param isLeapYear 是否闰年
* @return 是否匹配
*/
private static boolean isMatchDayOfMonth(ValueMatcher matcher, int dayOfMonth, int month, boolean isLeapYear) {
return ((matcher instanceof DayOfMonthValueMatcher) //
? ((DayOfMonthValueMatcher) matcher).match(dayOfMonth, month, isLeapYear) //
: matcher.match(dayOfMonth));
}
}

View File

@ -10,14 +10,14 @@ import java.util.List;
*
* @author Looly
*/
public class DayOfMonthValueMatcher extends BoolArrayValueMatcher {
public class DayOfMonthMatcher extends BoolArrayMatcher {
/**
* 构造
*
* @param intValueList 匹配的日值
*/
public DayOfMonthValueMatcher(List<Integer> intValueList) {
public DayOfMonthMatcher(List<Integer> intValueList) {
super(intValueList);
}

View File

@ -1,157 +0,0 @@
package cn.hutool.cron.pattern.matcher;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Console;
import cn.hutool.core.lang.mutable.MutableBool;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.TimeZone;
/**
* 时间匹配表用于存放定时任务表达式解析后的结构信息
*
* @author looly
* @since 5.8.0
*/
public class MatcherTable {
/**
* 秒字段匹配列表
*/
public final List<DateTimeMatcher> matchers;
/**
* 构造
*
* @param size 表达式个数用于表示复合表达式中单个表达式个数
*/
public MatcherTable(int size) {
matchers = new ArrayList<>(size);
}
/**
* 获取下一个最近的匹配日期时间
*
* @param second
* @param minute
* @param hour
* @param dayOfMonth
* @param month 从0开始
* @param dayOfWeek
* @param year
* @param zone 时区
* @return {@link Calendar}
*/
public Calendar nextMatchAfter(int second, int minute, int hour, int dayOfMonth, int month, int dayOfWeek, int year, TimeZone zone) {
List<Calendar> nextMatchs = new ArrayList<>(second);
for (DateTimeMatcher matcher : matchers) {
nextMatchs.add(singleNextMatchAfter(matcher, second, minute, hour,
dayOfMonth, month, dayOfWeek, year, zone));
}
// 返回匹配到的最早日期
return CollUtil.min(nextMatchs);
}
/**
* 获取下一个匹配日期时间
*
* @param matcher 匹配器
* @param second
* @param minute
* @param hour
* @param dayOfMonth
* @param month 从0开始
* @param dayOfWeek
* @param year
* @param zone 时区
* @return {@link Calendar}
*/
private static Calendar singleNextMatchAfter(DateTimeMatcher matcher, int second, int minute, int hour,
int dayOfMonth, int month, int dayOfWeek, int year, TimeZone zone) {
Calendar calendar = Calendar.getInstance(zone);
// 上一个字段不一致说明产生了新值下一个字段使用最小值
MutableBool isNextEquals = new MutableBool(true);
//
final int nextYear = nextAfter(matcher.yearMatcher, year, isNextEquals);
calendar.set(Calendar.YEAR, nextYear);
//
final int nextDayOfWeek = nextAfter(matcher.dayOfWeekMatcher, dayOfWeek, isNextEquals);
calendar.set(Calendar.DAY_OF_WEEK, nextDayOfWeek);
//
final int nextMonth = nextAfter(matcher.monthMatcher, month + 1, isNextEquals);
calendar.set(Calendar.MONTH, nextMonth - 1);
//
final int nextDayOfMonth = nextAfter(matcher.dayOfMonthMatcher, dayOfMonth, isNextEquals);
calendar.set(Calendar.DAY_OF_MONTH, nextDayOfMonth);
//
final int nextHour = nextAfter(matcher.hourMatcher, hour, isNextEquals);
calendar.set(Calendar.HOUR, nextHour);
//
int nextMinute = nextAfter(matcher.minuteMatcher, minute, isNextEquals);
calendar.set(Calendar.MINUTE, nextMinute);
//
final int nextSecond = nextAfter(matcher.secondMatcher, second, isNextEquals);
calendar.set(Calendar.SECOND, nextSecond);
Console.log(nextYear, nextDayOfWeek, nextMonth, nextDayOfMonth, nextHour, nextMinute, nextSecond);
return calendar;
}
/**
* 获取对应字段匹配器的下一个值
*
* @param matcher 匹配器
* @param value
* @param isNextEquals 是否下一个值和值相同不同获取初始值相同获取下一值然后修改
* @return 下一个值-1标识匹配所有值的情况应获取整个字段的最小值
*/
private static int nextAfter(ValueMatcher matcher, int value, MutableBool isNextEquals) {
int nextValue;
if (isNextEquals.get()) {
// 上一层级得到相同值下级获取下个值
nextValue = matcher.nextAfter(value);
isNextEquals.set(nextValue == value);
} else {
// 上一层级的值得到了不同值下级的所有值使用最小值
if (matcher instanceof AlwaysTrueValueMatcher) {
nextValue = value;
} else if (matcher instanceof BoolArrayValueMatcher) {
nextValue = ((BoolArrayValueMatcher) matcher).getMinValue();
} else {
throw new IllegalArgumentException("Invalid matcher: " + matcher.getClass().getName());
}
}
return nextValue;
}
/**
* 给定时间是否匹配定时任务表达式
*
* @param second 秒数-1表示不匹配此项
* @param minute 分钟
* @param hour 小时
* @param dayOfMonth
* @param month 从1开始
* @param dayOfWeek 从0开始0和7都表示周日
* @param year
* @return 如果匹配返回 {@code true}, 否则返回 {@code false}
*/
public boolean match(int second, int minute, int hour, int dayOfMonth, int month, int dayOfWeek, int year) {
for (DateTimeMatcher matcher : matchers) {
if (matcher.match(second, minute, hour, dayOfMonth, month, dayOfWeek, year)) {
return true;
}
}
return false;
}
}

View File

@ -3,12 +3,12 @@ package cn.hutool.cron.pattern.matcher;
import cn.hutool.core.lang.Matcher;
/**
* 匹配器<br>
* 表达式中的某个位置部分匹配器<br>
* 用于匹配日期位中对应数字是否匹配
*
* @author Looly
*/
public interface ValueMatcher extends Matcher<Integer> {
public interface PartMatcher extends Matcher<Integer> {
/**
* 获取指定值之后的匹配值也可以是指定值本身
* @param value 指定的值

View File

@ -0,0 +1,166 @@
package cn.hutool.cron.pattern.matcher;
import cn.hutool.core.lang.mutable.MutableBool;
import cn.hutool.cron.pattern.Part;
import java.time.Year;
import java.util.Calendar;
import java.util.TimeZone;
/**
* 单一表达式的匹配器匹配器由7个{@link PartMatcher}组成
*
* @author looly
* @since 5.8.0
*/
public class PatternMatcher {
private final PartMatcher[] matchers;
public PatternMatcher(PartMatcher secondMatcher,
PartMatcher minuteMatcher,
PartMatcher hourMatcher,
PartMatcher dayOfMonthMatcher,
PartMatcher monthMatcher,
PartMatcher dayOfWeekMatcher,
PartMatcher yearMatcher) {
matchers = new PartMatcher[]{
secondMatcher,
minuteMatcher,
hourMatcher,
dayOfMonthMatcher,
monthMatcher,
dayOfWeekMatcher,
yearMatcher
};
}
/**
* 根据表达式位置获取对应的{@link PartMatcher}
* @param part 表达式位置
* @return {@link PartMatcher}
*/
public PartMatcher get(Part part){
return matchers[part.ordinal()];
}
//region match
/**
* 给定时间是否匹配定时任务表达式
*
* @param second 秒数-1表示不匹配此项
* @param minute 分钟
* @param hour 小时
* @param dayOfMonth
* @param month 从1开始
* @param dayOfWeek 从0开始0和7都表示周日
* @param year
* @return 如果匹配返回 {@code true}, 否则返回 {@code false}
*/
public boolean match(int second, int minute, int hour, int dayOfMonth, int month, int dayOfWeek, int year) {
return ((second < 0) || matchers[0].match(second)) // 匹配秒非秒匹配模式下始终返回true
&& matchers[1].match(minute)// 匹配分
&& matchers[2].match(hour)// 匹配时
&& isMatchDayOfMonth(matchers[3], dayOfMonth, month, Year.isLeap(year))// 匹配日
&& matchers[4].match(month) // 匹配月
&& matchers[5].match(dayOfWeek)// 匹配周
&& matchers[6].match(year);// 匹配年
}
/**
* 是否匹配日指定月份的第几天
*
* @param matcher {@link PartMatcher}
* @param dayOfMonth
* @param month
* @param isLeapYear 是否闰年
* @return 是否匹配
*/
private static boolean isMatchDayOfMonth(PartMatcher matcher, int dayOfMonth, int month, boolean isLeapYear) {
return ((matcher instanceof DayOfMonthMatcher) //
? ((DayOfMonthMatcher) matcher).match(dayOfMonth, month, isLeapYear) //
: matcher.match(dayOfMonth));
}
//endregion
//region nextMatchAfter
/**
* 获取下一个匹配日期时间
*
* @param second
* @param minute
* @param hour
* @param dayOfMonth
* @param month 从1开始
* @param dayOfWeek 从0开始, 0表示周日
* @param year
* @param zone 时区
* @return {@link Calendar}
*/
public Calendar nextMatchAfter(int second, int minute, int hour,
int dayOfMonth, int month, int dayOfWeek, int year, 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);
//
final int nextDayOfWeek = nextAfter(get(Part.DAY_OF_WEEK), dayOfWeek, isNextEquals);
calendar.set(Calendar.DAY_OF_WEEK, nextDayOfWeek + 1);
//
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);
return calendar;
}
/**
* 获取对应字段匹配器的下一个值
*
* @param matcher 匹配器
* @param value
* @param isNextEquals 是否下一个值和值相同不同获取初始值相同获取下一值然后修改
* @return 下一个值-1标识匹配所有值的情况应获取整个字段的最小值
*/
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());
}
}
return nextValue;
}
//endregion
}

View File

@ -9,7 +9,7 @@ import java.util.LinkedHashSet;
*
* @author Looly
*/
public class YearValueMatcher implements ValueMatcher {
public class YearValueMatcher implements PartMatcher {
private final LinkedHashSet<Integer> valueList;

View File

@ -7,17 +7,17 @@ import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.cron.CronException;
import cn.hutool.cron.pattern.Part;
import cn.hutool.cron.pattern.matcher.AlwaysTrueValueMatcher;
import cn.hutool.cron.pattern.matcher.BoolArrayValueMatcher;
import cn.hutool.cron.pattern.matcher.DayOfMonthValueMatcher;
import cn.hutool.cron.pattern.matcher.ValueMatcher;
import cn.hutool.cron.pattern.matcher.AlwaysTrueMatcher;
import cn.hutool.cron.pattern.matcher.BoolArrayMatcher;
import cn.hutool.cron.pattern.matcher.DayOfMonthMatcher;
import cn.hutool.cron.pattern.matcher.PartMatcher;
import cn.hutool.cron.pattern.matcher.YearValueMatcher;
import java.util.ArrayList;
import java.util.List;
/**
* 定时任务表达式各个部分的解析器根据{@link Part}指定不同部分解析为{@link ValueMatcher}<br>
* 定时任务表达式各个部分的解析器根据{@link Part}指定不同部分解析为{@link PartMatcher}<br>
* 每个部分支持
* <ul>
* <li><strong>*</strong> 表示匹配这个位置所有的时间</li>
@ -56,21 +56,21 @@ public class PartParser {
}
/**
* 将表达式解析为{@link ValueMatcher}<br>
* 将表达式解析为{@link PartMatcher}<br>
* <ul>
* <li>* 或者 ? 返回{@link AlwaysTrueValueMatcher}</li>
* <li>{@link Part#DAY_OF_MONTH} 返回{@link DayOfMonthValueMatcher}</li>
* <li>* 或者 ? 返回{@link AlwaysTrueMatcher}</li>
* <li>{@link Part#DAY_OF_MONTH} 返回{@link DayOfMonthMatcher}</li>
* <li>{@link Part#YEAR} 返回{@link YearValueMatcher}</li>
* <li>其他 返回{@link BoolArrayValueMatcher}</li>
* <li>其他 返回{@link BoolArrayMatcher}</li>
* </ul>
*
* @param value 表达式
* @return {@link ValueMatcher}
* @return {@link PartMatcher}
*/
public ValueMatcher parseAsValueMatcher(String value) {
public PartMatcher parse(String value) {
if (isMatchAllStr(value)) {
//兼容Quartz的"?"表达式不会出现互斥情况"*"作用相同
return new AlwaysTrueValueMatcher();
return new AlwaysTrueMatcher();
}
final List<Integer> values = parseArray(value);
@ -80,11 +80,11 @@ public class PartParser {
switch (this.part) {
case DAY_OF_MONTH:
return new DayOfMonthValueMatcher(values);
return new DayOfMonthMatcher(values);
case YEAR:
return new YearValueMatcher(values);
default:
return new BoolArrayValueMatcher(values);
return new BoolArrayMatcher(values);
}
}
@ -243,6 +243,11 @@ public class PartParser {
i = parseAlias(value);
}
// 支持负数
if(i < 0){
i += part.getMax();
}
// 周日可以用0或7表示统一转换为0
if(Part.DAY_OF_WEEK.equals(this.part) && Week.SUNDAY.getIso8601Value() == i){
i = Week.SUNDAY.ordinal();

View File

@ -4,15 +4,15 @@ import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.cron.CronException;
import cn.hutool.cron.pattern.Part;
import cn.hutool.cron.pattern.matcher.AlwaysTrueValueMatcher;
import cn.hutool.cron.pattern.matcher.DateTimeMatcher;
import cn.hutool.cron.pattern.matcher.MatcherTable;
import cn.hutool.cron.pattern.matcher.ValueMatcher;
import cn.hutool.cron.pattern.matcher.AlwaysTrueMatcher;
import cn.hutool.cron.pattern.matcher.PartMatcher;
import cn.hutool.cron.pattern.matcher.PatternMatcher;
import java.util.ArrayList;
import java.util.List;
/**
* 定时任务表达式解析器用于将表达式字符串解析为{@link MatcherTable}
* 定时任务表达式解析器用于将表达式字符串解析为{@link PatternMatcher}的列表
*
* @author looly
* @since 5.8.0
@ -28,12 +28,12 @@ public class PatternParser {
private static final PartParser YEAR_VALUE_PARSER = PartParser.of(Part.YEAR);
/**
* 解析表达式到匹配表中
* 解析表达式到匹配表中
*
* @param cronPattern 复合表达式
* @return {@link MatcherTable}
* @return {@link List}
*/
public static MatcherTable parse(String cronPattern) {
public static List<PatternMatcher> parse(String cronPattern) {
return parseGroupPattern(cronPattern);
}
@ -44,24 +44,24 @@ public class PatternParser {
* </pre>
*
* @param groupPattern 复合表达式
* @return {@link MatcherTable}
* @return {@link List}
*/
private static MatcherTable parseGroupPattern(String groupPattern) {
private static List<PatternMatcher> parseGroupPattern(String groupPattern) {
final List<String> patternList = StrUtil.splitTrim(groupPattern, '|');
final MatcherTable matcherTable = new MatcherTable(patternList.size());
final List<PatternMatcher> patternMatchers = new ArrayList<>(patternList.size());
for (String pattern : patternList) {
matcherTable.matchers.add(parseSinglePattern(pattern));
patternMatchers.add(parseSinglePattern(pattern));
}
return matcherTable;
return patternMatchers;
}
/**
* 解析单一定时任务表达式
*
* @param pattern 表达式
* @return {@link DateTimeMatcher}
* @return {@link PatternMatcher}
*/
private static DateTimeMatcher parseSinglePattern(String pattern) {
private static PatternMatcher parseSinglePattern(String pattern) {
final String[] parts = pattern.split("\\s+");
Assert.checkBetween(parts.length, 5, 7,
() -> new CronException("Pattern [{}] is invalid, it must be 5-7 parts!", pattern));
@ -76,26 +76,26 @@ public class PatternParser {
final String secondPart = (1 == offset) ? parts[0] : "0";
//
ValueMatcher yearMatcher;
PartMatcher yearMatcher;
if (parts.length == 7) {// 支持年的表达式
yearMatcher = YEAR_VALUE_PARSER.parseAsValueMatcher(parts[6]);
yearMatcher = YEAR_VALUE_PARSER.parse(parts[6]);
} else {// 不支持年的表达式全部匹配
yearMatcher = AlwaysTrueValueMatcher.INSTANCE;
yearMatcher = AlwaysTrueMatcher.INSTANCE;
}
return new DateTimeMatcher(
return new PatternMatcher(
//
SECOND_VALUE_PARSER.parseAsValueMatcher(secondPart),
SECOND_VALUE_PARSER.parse(secondPart),
//
MINUTE_VALUE_PARSER.parseAsValueMatcher(parts[offset]),
MINUTE_VALUE_PARSER.parse(parts[offset]),
//
HOUR_VALUE_PARSER.parseAsValueMatcher(parts[1 + offset]),
HOUR_VALUE_PARSER.parse(parts[1 + offset]),
//
DAY_OF_MONTH_VALUE_PARSER.parseAsValueMatcher(parts[2 + offset]),
DAY_OF_MONTH_VALUE_PARSER.parse(parts[2 + offset]),
//
MONTH_VALUE_PARSER.parseAsValueMatcher(parts[3 + offset]),
MONTH_VALUE_PARSER.parse(parts[3 + offset]),
//
DAY_OF_WEEK_VALUE_PARSER.parseAsValueMatcher(parts[4 + offset]),
DAY_OF_WEEK_VALUE_PARSER.parse(parts[4 + offset]),
//
yearMatcher
);

View File

@ -2,6 +2,7 @@ package cn.hutool.cron.pattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Console;
import org.junit.Assert;
import org.junit.Test;
import java.util.Calendar;
@ -13,7 +14,9 @@ public class CronPatternNextMatchTest {
CronPattern pattern = new CronPattern("23 12 * 12 * * *");
//noinspection ConstantConditions
final Calendar calendar = pattern.nextMatchAfter(
DateUtil.parse("2022-04-12 09:12:23").toCalendar());
DateUtil.parse("2022-04-12 09:12:12").toCalendar());
Console.log(DateUtil.date(calendar));
Assert.assertTrue(pattern.match(calendar, true));
}
}

View File

@ -122,6 +122,14 @@ public class CronPatternTest {
assertMatch(pattern, "2017-02-19 04:00:33");
}
@Test
public void patternNegativeTest() {
// -4表示倒数的数字此处在小时上-4表示 23 - 4为19
CronPattern pattern = CronPattern.of("* 0 -4 * * ?");
assertMatch(pattern, "2017-02-09 19:00:00");
assertMatch(pattern, "2017-02-19 19:00:33");
}
@Test
public void rangePatternTest() {
CronPattern pattern = CronPattern.of("* 20/2 * * * ?");