mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-07-21 15:09:48 +08:00
change parser
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
package cn.hutool.cron.pattern;
|
||||
|
||||
import cn.hutool.cron.pattern.matcher.MatcherTable;
|
||||
import cn.hutool.cron.pattern.parser.CronPatternParser;
|
||||
import cn.hutool.cron.pattern.parser.PatternParser;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
@@ -68,6 +68,17 @@ public class CronPattern {
|
||||
private final String pattern;
|
||||
private final MatcherTable matcherTable;
|
||||
|
||||
/**
|
||||
* 解析表达式为 CronPattern
|
||||
*
|
||||
* @param pattern 表达式
|
||||
* @return CronPattern
|
||||
* @since 5.8.0
|
||||
*/
|
||||
public static CronPattern of(String pattern) {
|
||||
return new CronPattern(pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
@@ -75,10 +86,11 @@ public class CronPattern {
|
||||
*/
|
||||
public CronPattern(String pattern) {
|
||||
this.pattern = pattern;
|
||||
this.matcherTable = CronPatternParser.parse(pattern);
|
||||
this.matcherTable = PatternParser.parse(pattern);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------- match start
|
||||
|
||||
/**
|
||||
* 给定时间是否匹配定时任务表达式
|
||||
*
|
||||
@@ -104,6 +116,24 @@ 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());
|
||||
}
|
||||
|
||||
/**
|
||||
* 给定时间是否匹配定时任务表达式
|
||||
*
|
||||
@@ -111,7 +141,7 @@ public class CronPattern {
|
||||
* @param isMatchSecond 是否匹配秒
|
||||
* @return 如果匹配返回 {@code true}, 否则返回 {@code false}
|
||||
*/
|
||||
public boolean match(GregorianCalendar calendar, boolean isMatchSecond) {
|
||||
public boolean match(Calendar calendar, boolean isMatchSecond) {
|
||||
final int second = isMatchSecond ? calendar.get(Calendar.SECOND) : -1;
|
||||
final int minute = calendar.get(Calendar.MINUTE);
|
||||
final int hour = calendar.get(Calendar.HOUR_OF_DAY);
|
||||
|
@@ -0,0 +1,23 @@
|
||||
package cn.hutool.cron.pattern;
|
||||
|
||||
import cn.hutool.core.builder.Builder;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
public class CronPatternBuilder implements Builder<String> {
|
||||
|
||||
final String[] parts = new String[7];
|
||||
|
||||
public static CronPatternBuilder of(){
|
||||
return new CronPatternBuilder();
|
||||
}
|
||||
|
||||
public CronPatternBuilder set(Part part, String value){
|
||||
parts[part.ordinal()] = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String build() {
|
||||
return StrUtil.join(StrUtil.SPACE, (Object[]) parts);
|
||||
}
|
||||
}
|
73
hutool-cron/src/main/java/cn/hutool/cron/pattern/Part.java
Normal file
73
hutool-cron/src/main/java/cn/hutool/cron/pattern/Part.java
Normal file
@@ -0,0 +1,73 @@
|
||||
package cn.hutool.cron.pattern;
|
||||
|
||||
import cn.hutool.core.date.Month;
|
||||
import cn.hutool.core.date.Week;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.cron.CronException;
|
||||
|
||||
/**
|
||||
* 表达式各个部分的枚举,用于限定位置和规则<br>
|
||||
* {@link #ordinal()}表示此部分在表达式中的位置,如0表示秒
|
||||
*
|
||||
* @author looly
|
||||
* @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);
|
||||
|
||||
private final int min;
|
||||
private final int max;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param min 限定最小值(包含)
|
||||
* @param max 限定最大值(包含)
|
||||
*/
|
||||
Part(int min, int max) {
|
||||
if (min > max) {
|
||||
this.min = max;
|
||||
this.max = min;
|
||||
} else {
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最小值
|
||||
*
|
||||
* @return 最小值
|
||||
*/
|
||||
public int getMin() {
|
||||
return this.min;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最大值
|
||||
*
|
||||
* @return 最大值
|
||||
*/
|
||||
public int getMax() {
|
||||
return this.max;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查单个值是否有效
|
||||
*
|
||||
* @param value 值
|
||||
* @return 检查后的值
|
||||
* @throws CronException 检查无效抛出此异常
|
||||
*/
|
||||
public int checkValue(int value) throws CronException {
|
||||
Assert.checkBetween(value, min, max,
|
||||
() -> new CronException("Value {} out of range: [{} , {}]", value, min, max));
|
||||
return value;
|
||||
}
|
||||
}
|
@@ -31,8 +31,8 @@ public class DayOfMonthValueMatcher extends BoolArrayValueMatcher {
|
||||
*/
|
||||
public boolean match(int value, int month, boolean isLeapYear) {
|
||||
return (super.match(value) // 在约定日范围内的某一天
|
||||
//匹配器中用户定义了最后一天(32表示最后一天)
|
||||
|| (value > 27 && match(32) && isLastDayOfMonth(value, month, isLeapYear)));
|
||||
//匹配器中用户定义了最后一天(31表示最后一天)
|
||||
|| (value > 27 && match(31) && isLastDayOfMonth(value, month, isLeapYear)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,10 +1,13 @@
|
||||
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.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* 时间匹配表,用于存放定时任务表达式解析后的结构信息
|
||||
@@ -28,54 +31,107 @@ public class MatcherTable {
|
||||
matchers = new ArrayList<>(size);
|
||||
}
|
||||
|
||||
public LocalDateTime nextMatchAfter(int second, int minute, int hour, int dayOfMonth, int month, int dayOfWeek, int year) {
|
||||
List<LocalDateTime> nextMatchs = new ArrayList<>(second);
|
||||
/**
|
||||
* 获取下一个最近的匹配日期时间
|
||||
*
|
||||
* @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));
|
||||
dayOfMonth, month, dayOfWeek, year, zone));
|
||||
}
|
||||
// 返回最先匹配到的日期
|
||||
// 返回匹配到的最早日期
|
||||
return CollUtil.min(nextMatchs);
|
||||
}
|
||||
|
||||
private static LocalDateTime singleNextMatchAfter(DateTimeMatcher matcher, int second, int minute, int hour,
|
||||
int dayOfMonth, int month, int dayOfWeek, int year){
|
||||
boolean isNextNotEquals = true;
|
||||
/**
|
||||
* 获取下一个匹配日期时间
|
||||
*
|
||||
* @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 = matcher.yearMatcher.nextAfter(year);
|
||||
isNextNotEquals &= (year != nextYear);
|
||||
final int nextYear = nextAfter(matcher.yearMatcher, year, isNextEquals);
|
||||
calendar.set(Calendar.YEAR, nextYear);
|
||||
|
||||
// 周
|
||||
int nextDayOfWeek;
|
||||
if(isNextNotEquals){
|
||||
// 上一个字段不一致,说明产生了新值,本字段使用最小值
|
||||
nextDayOfWeek = ((BoolArrayValueMatcher)matcher.dayOfWeekMatcher).getMinValue();
|
||||
}else{
|
||||
nextDayOfWeek = matcher.dayOfWeekMatcher.nextAfter(dayOfWeek);
|
||||
isNextNotEquals &= (dayOfWeek != nextDayOfWeek);
|
||||
}
|
||||
final int nextDayOfWeek = nextAfter(matcher.dayOfWeekMatcher, dayOfWeek, isNextEquals);
|
||||
calendar.set(Calendar.DAY_OF_WEEK, nextDayOfWeek);
|
||||
|
||||
// 月
|
||||
int nextMonth;
|
||||
if(isNextNotEquals){
|
||||
// 上一个字段不一致,说明产生了新值,本字段使用最小值
|
||||
nextMonth = ((BoolArrayValueMatcher)matcher.monthMatcher).getMinValue();
|
||||
}else{
|
||||
nextMonth = matcher.monthMatcher.nextAfter(dayOfWeek);
|
||||
isNextNotEquals &= (month != nextMonth);
|
||||
}
|
||||
final int nextMonth = nextAfter(matcher.monthMatcher, month + 1, isNextEquals);
|
||||
calendar.set(Calendar.MONTH, nextMonth - 1);
|
||||
|
||||
// 日
|
||||
int nextDayOfMonth;
|
||||
if(isNextNotEquals){
|
||||
// 上一个字段不一致,说明产生了新值,本字段使用最小值
|
||||
nextDayOfMonth = ((BoolArrayValueMatcher)matcher.dayOfMonthMatcher).getMinValue();
|
||||
}else{
|
||||
nextDayOfMonth = matcher.dayOfMonthMatcher.nextAfter(dayOfWeek);
|
||||
isNextNotEquals &= (dayOfMonth != nextDayOfMonth);
|
||||
}
|
||||
final int nextDayOfMonth = nextAfter(matcher.dayOfMonthMatcher, dayOfMonth, isNextEquals);
|
||||
calendar.set(Calendar.DAY_OF_MONTH, nextDayOfMonth);
|
||||
|
||||
return null;
|
||||
// 时
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,8 +141,8 @@ public class MatcherTable {
|
||||
* @param minute 分钟
|
||||
* @param hour 小时
|
||||
* @param dayOfMonth 天
|
||||
* @param month 月
|
||||
* @param dayOfWeek 周几
|
||||
* @param month 月,从1开始
|
||||
* @param dayOfWeek 周,从0开始,0和7都表示周日
|
||||
* @param year 年
|
||||
* @return 如果匹配返回 {@code true}, 否则返回 {@code false}
|
||||
*/
|
||||
|
@@ -1,39 +0,0 @@
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
import cn.hutool.cron.CronException;
|
||||
import cn.hutool.cron.pattern.matcher.DayOfMonthValueMatcher;
|
||||
import cn.hutool.cron.pattern.matcher.ValueMatcher;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 每月的几号值处理<br>
|
||||
* 每月最多31天,32和“L”都表示最后一天
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class DayOfMonthValueParser extends AbsValueParser {
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
public DayOfMonthValueParser() {
|
||||
super(1, 31);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int parse(String value) throws CronException {
|
||||
if ("L".equalsIgnoreCase(value) || "32".equals(value)) {// 每月最后一天
|
||||
return 32;
|
||||
} else {
|
||||
return super.parse(value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ValueMatcher buildValueMatcher(List<Integer> values) {
|
||||
//考虑每月的天数不同,且存在闰年情况,日匹配单独使用
|
||||
return new DayOfMonthValueMatcher(values);
|
||||
}
|
||||
}
|
@@ -1,56 +0,0 @@
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
import cn.hutool.cron.CronException;
|
||||
|
||||
/**
|
||||
* 星期值处理<br>
|
||||
* 1表示星期一,2表示星期二,依次类推,0和7都可以表示星期日<br>
|
||||
* {@code L}表示周六
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class DayOfWeekValueParser extends AbsValueParser {
|
||||
|
||||
/**
|
||||
* Weeks aliases.
|
||||
*/
|
||||
private static final String[] ALIASES = {"sun", "mon", "tue", "wed", "thu", "fri", "sat"};
|
||||
|
||||
public DayOfWeekValueParser() {
|
||||
super(0, 7);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对于星期提供转换<br>
|
||||
* 1表示星期一,2表示星期二,依次类推,0和7都可以表示星期日
|
||||
*/
|
||||
@Override
|
||||
public int parse(String value) throws CronException {
|
||||
try {
|
||||
return super.parse(value) % 7;
|
||||
} catch (Exception e) {
|
||||
return parseAlias(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析别名
|
||||
*
|
||||
* @param value 别名值
|
||||
* @return 月份int值
|
||||
* @throws CronException 无效别名抛出此异常
|
||||
*/
|
||||
private int parseAlias(String value) throws CronException {
|
||||
if ("L".equalsIgnoreCase(value)) {
|
||||
//最后一天为星期六
|
||||
return ALIASES.length - 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < ALIASES.length; i++) {
|
||||
if (ALIASES[i].equalsIgnoreCase(value)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
throw new CronException("Invalid month alias: {}", value);
|
||||
}
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
/**
|
||||
* 小时值处理<br>
|
||||
* 小时被限定在0-23
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class HourValueParser extends AbsValueParser {
|
||||
public HourValueParser() {
|
||||
super(0, 23);
|
||||
}
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
/**
|
||||
* 分钟值处理<br>
|
||||
* 限定于0-59
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class MinuteValueParser extends AbsValueParser {
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
public MinuteValueParser() {
|
||||
super(0, 59);
|
||||
}
|
||||
}
|
@@ -1,40 +0,0 @@
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
import cn.hutool.core.date.Month;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.cron.CronException;
|
||||
|
||||
/**
|
||||
* 月份值处理<br>
|
||||
* 限定于1-12,1表示一月,支持别名(忽略大小写),如一月是{@code jan}
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class MonthValueParser extends AbsValueParser {
|
||||
|
||||
public MonthValueParser() {
|
||||
super(1, 12);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int parse(String value) throws CronException {
|
||||
try {
|
||||
return super.parse(value);
|
||||
} catch (Exception e) {
|
||||
return parseAlias(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析别名
|
||||
*
|
||||
* @param monthName 别名值
|
||||
* @return 月份int值,从1开始
|
||||
* @throws CronException 无效月别名抛出此异常
|
||||
*/
|
||||
private int parseAlias(String monthName) throws CronException {
|
||||
final Month month = Month.of(monthName);
|
||||
Assert.notNull(month, () -> new CronException("Invalid month alias: {}", monthName));
|
||||
return month.getValueBaseOne();
|
||||
}
|
||||
}
|
@@ -1,109 +1,75 @@
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.date.Month;
|
||||
import cn.hutool.core.date.Week;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
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.YearValueMatcher;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 简易值转换器。将给定String值转为int,并限定最大值和最小值<br>
|
||||
* 此类同时识别{@code L} 为最大值。
|
||||
* 定时任务表达式各个部分的解析器
|
||||
*
|
||||
* @author Looly
|
||||
* @author looly
|
||||
* @since 5.8.0
|
||||
*/
|
||||
public abstract class AbsValueParser implements ValueParser {
|
||||
public class PartParser {
|
||||
|
||||
private final Part part;
|
||||
|
||||
/**
|
||||
* 最小值(包括)
|
||||
* 创建解析器
|
||||
*
|
||||
* @param part 对应解析的部分枚举
|
||||
* @return 解析器
|
||||
*/
|
||||
protected int min;
|
||||
/**
|
||||
* 最大值(包括)
|
||||
*/
|
||||
protected int max;
|
||||
public static PartParser of(Part part) {
|
||||
return new PartParser(part);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param min 最小值(包括)
|
||||
* @param max 最大值(包括)
|
||||
* @param part 对应解析的部分枚举
|
||||
*/
|
||||
public AbsValueParser(int min, int max) {
|
||||
if (min > max) {
|
||||
this.min = max;
|
||||
this.max = min;
|
||||
} else {
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int parse(String value) throws CronException {
|
||||
if ("L".equalsIgnoreCase(value)) {
|
||||
// L表示最大值
|
||||
return max;
|
||||
}
|
||||
|
||||
int i;
|
||||
try {
|
||||
i = Integer.parseInt(value);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new CronException(e, "Invalid integer value: '{}'", value);
|
||||
}
|
||||
if (i < min || i > max) {
|
||||
throw new CronException("Value {} out of range: [{} , {}]", i, min, max);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMin() {
|
||||
return this.min;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMax() {
|
||||
return this.max;
|
||||
public PartParser(Part part) {
|
||||
this.part = part;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理定时任务表达式每个时间字段<br>
|
||||
* 多个时间使用逗号分隔
|
||||
* 将表达式解析为{@link ValueMatcher}
|
||||
*
|
||||
* @param value 某个时间字段
|
||||
* @return List
|
||||
* @param value 表达式
|
||||
* @return {@link ValueMatcher}
|
||||
*/
|
||||
@Override
|
||||
public ValueMatcher parseAsValueMatcher(String value) {
|
||||
if (isMatchAllStr(value)) {
|
||||
//兼容Quartz的"?"表达式,不会出现互斥情况,与"*"作用相同
|
||||
return new AlwaysTrueValueMatcher();
|
||||
}
|
||||
|
||||
List<Integer> values = parseArray(value);
|
||||
final List<Integer> values = parseArray(value);
|
||||
if (values.size() == 0) {
|
||||
throw new CronException("Invalid field: [{}]", value);
|
||||
throw new CronException("Invalid part value: [{}]", value);
|
||||
}
|
||||
|
||||
return buildValueMatcher(values);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据解析的数字值列表构建{@link ValueMatcher}<br>
|
||||
* 默认为{@link BoolArrayValueMatcher},如果有特殊实现,子类须重写此方法
|
||||
*
|
||||
* @param values 数字值列表
|
||||
* @return {@link ValueMatcher}
|
||||
*/
|
||||
protected ValueMatcher buildValueMatcher(List<Integer> values){
|
||||
return new BoolArrayValueMatcher(values);
|
||||
switch (this.part) {
|
||||
case DAY_OF_MONTH:
|
||||
return new DayOfMonthValueMatcher(values);
|
||||
case YEAR:
|
||||
return new YearValueMatcher(values);
|
||||
default:
|
||||
return new BoolArrayValueMatcher(values);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,10 +79,11 @@ public abstract class AbsValueParser implements ValueParser {
|
||||
* <li><strong>a</strong> 或 <strong>*</strong></li>
|
||||
* <li><strong>a,b,c,d</strong></li>
|
||||
* </ul>
|
||||
*
|
||||
* @param value 子表达式值
|
||||
* @return 值列表
|
||||
*/
|
||||
private List<Integer> parseArray(String value){
|
||||
private List<Integer> parseArray(String value) {
|
||||
final List<Integer> values = new ArrayList<>();
|
||||
|
||||
final List<String> parts = StrUtil.split(value, StrUtil.C_COMMA);
|
||||
@@ -146,7 +113,7 @@ public abstract class AbsValueParser implements ValueParser {
|
||||
if (size == 1) {// 普通形式
|
||||
results = parseRange(value, -1);
|
||||
} else if (size == 2) {// 间隔形式
|
||||
final int step = parse(parts.get(1));
|
||||
final int step = parseNumber(parts.get(1));
|
||||
if (step < 1) {
|
||||
throw new CronException("Non positive divisor for field: [{}]", value);
|
||||
}
|
||||
@@ -168,7 +135,7 @@ public abstract class AbsValueParser implements ValueParser {
|
||||
* </ul>
|
||||
*
|
||||
* @param value 范围表达式
|
||||
* @param step 步进
|
||||
* @param step 步进
|
||||
* @return List
|
||||
*/
|
||||
private List<Integer> parseRange(String value, int step) {
|
||||
@@ -177,22 +144,22 @@ public abstract class AbsValueParser implements ValueParser {
|
||||
// 全部匹配形式
|
||||
if (value.length() <= 2) {
|
||||
//根据步进的第一个数字确定起始时间,类似于 12/3则从12(秒、分等)开始
|
||||
int minValue = getMin();
|
||||
if(false == isMatchAllStr(value)) {
|
||||
minValue = Math.max(minValue, parse(value));
|
||||
}else {
|
||||
int minValue = part.getMin();
|
||||
if (false == isMatchAllStr(value)) {
|
||||
minValue = Math.max(minValue, parseNumber(value));
|
||||
} else {
|
||||
//在全匹配模式下,如果步进不存在,表示步进为1
|
||||
if(step < 1) {
|
||||
if (step < 1) {
|
||||
step = 1;
|
||||
}
|
||||
}
|
||||
if(step > 0) {
|
||||
final int maxValue = getMax();
|
||||
if(minValue > maxValue) {
|
||||
if (step > 0) {
|
||||
final int maxValue = part.getMax();
|
||||
if (minValue > maxValue) {
|
||||
throw new CronException("Invalid value {} > {}", minValue, maxValue);
|
||||
}
|
||||
//有步进
|
||||
for (int i = minValue; i <= maxValue; i+=step) {
|
||||
for (int i = minValue; i <= maxValue; i += step) {
|
||||
results.add(i);
|
||||
}
|
||||
} else {
|
||||
@@ -206,26 +173,26 @@ public abstract class AbsValueParser implements ValueParser {
|
||||
List<String> parts = StrUtil.split(value, '-');
|
||||
int size = parts.size();
|
||||
if (size == 1) {// 普通值
|
||||
final int v1 = parse(value);
|
||||
if(step > 0) {//类似 20/2的形式
|
||||
NumberUtil.appendRange(v1, getMax(), step, results);
|
||||
}else {
|
||||
final int v1 = parseNumber(value);
|
||||
if (step > 0) {//类似 20/2的形式
|
||||
NumberUtil.appendRange(v1, part.getMax(), step, results);
|
||||
} else {
|
||||
results.add(v1);
|
||||
}
|
||||
} else if (size == 2) {// range值
|
||||
final int v1 = parse(parts.get(0));
|
||||
final int v2 = parse(parts.get(1));
|
||||
if(step < 1) {
|
||||
final int v1 = parseNumber(parts.get(0));
|
||||
final int v2 = parseNumber(parts.get(1));
|
||||
if (step < 1) {
|
||||
//在range模式下,如果步进不存在,表示步进为1
|
||||
step = 1;
|
||||
}
|
||||
if (v1 < v2) {// 正常范围,例如:2-5
|
||||
NumberUtil.appendRange(v1, v2, step, results);
|
||||
} else if (v1 > v2) {// 逆向范围,反选模式,例如:5-2
|
||||
NumberUtil.appendRange(v1, getMax(), step, results);
|
||||
NumberUtil.appendRange(getMin(), v2, step, results);
|
||||
NumberUtil.appendRange(v1, part.getMax(), step, results);
|
||||
NumberUtil.appendRange(part.getMin(), v2, step, results);
|
||||
} else {// v1 == v2,此时与单值模式一致
|
||||
NumberUtil.appendRange(v1, getMax(), step, results);
|
||||
NumberUtil.appendRange(v1, part.getMax(), step, results);
|
||||
}
|
||||
} else {
|
||||
throw new CronException("Invalid syntax of field: [{}]", value);
|
||||
@@ -244,4 +211,56 @@ public abstract class AbsValueParser implements ValueParser {
|
||||
private static boolean isMatchAllStr(String value) {
|
||||
return (1 == value.length()) && ("*".equals(value) || "?".equals(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析单个int值,支持别名
|
||||
*
|
||||
* @param value 被解析的值
|
||||
* @return 解析结果
|
||||
* @throws CronException 当无效数字或无效别名时抛出
|
||||
*/
|
||||
private int parseNumber(String value) throws CronException {
|
||||
if ("L".equalsIgnoreCase(value)) {
|
||||
// L表示最大值
|
||||
return part.getMax();
|
||||
}
|
||||
|
||||
int i;
|
||||
try {
|
||||
i = Integer.parseInt(value);
|
||||
} catch (NumberFormatException ignore) {
|
||||
i = parseAlias(value);
|
||||
}
|
||||
|
||||
// 周日可以用0或7表示,统一转换为0
|
||||
if(this.part == Part.DAY_OF_WEEK && Week.SUNDAY.getIso8601Value() == i){
|
||||
i = Week.SUNDAY.ordinal();
|
||||
}
|
||||
|
||||
return part.checkValue(i);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析别名,只支持{@link Part#MONTH}和{@link Part#DAY_OF_WEEK}
|
||||
*
|
||||
* @param name 别名
|
||||
* @return 解析int值
|
||||
* @throws CronException 无匹配别名时抛出异常
|
||||
*/
|
||||
private int parseAlias(String name) throws CronException {
|
||||
switch (this.part) {
|
||||
case MONTH:
|
||||
final Month month = Month.of(name);
|
||||
Assert.notNull(month, () -> new CronException("Invalid month alias: {}", name));
|
||||
// 月份从1开始
|
||||
return month.getValueBaseOne();
|
||||
case DAY_OF_WEEK:
|
||||
final Week week = Week.of(name);
|
||||
Assert.notNull(week, () -> new CronException("Invalid day of week alias: {}", name));
|
||||
// 周从0开始,0表示周日
|
||||
return week.ordinal();
|
||||
}
|
||||
|
||||
throw new CronException("Invalid alias value: [{}]", name);
|
||||
}
|
||||
}
|
@@ -1,8 +1,9 @@
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
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;
|
||||
@@ -16,15 +17,15 @@ import java.util.List;
|
||||
* @author looly
|
||||
* @since 5.8.0
|
||||
*/
|
||||
public class CronPatternParser {
|
||||
public class PatternParser {
|
||||
|
||||
private static final ValueParser SECOND_VALUE_PARSER = new SecondValueParser();
|
||||
private static final ValueParser MINUTE_VALUE_PARSER = new MinuteValueParser();
|
||||
private static final ValueParser HOUR_VALUE_PARSER = new HourValueParser();
|
||||
private static final ValueParser DAY_OF_MONTH_VALUE_PARSER = new DayOfMonthValueParser();
|
||||
private static final ValueParser MONTH_VALUE_PARSER = new MonthValueParser();
|
||||
private static final ValueParser DAY_OF_WEEK_VALUE_PARSER = new DayOfWeekValueParser();
|
||||
private static final ValueParser YEAR_VALUE_PARSER = new YearValueParser();
|
||||
private static final PartParser SECOND_VALUE_PARSER = PartParser.of(Part.SECOND);
|
||||
private static final PartParser MINUTE_VALUE_PARSER = PartParser.of(Part.MINUTE);
|
||||
private static final PartParser HOUR_VALUE_PARSER = PartParser.of(Part.HOUR);
|
||||
private static final PartParser DAY_OF_MONTH_VALUE_PARSER = PartParser.of(Part.DAY_OF_MONTH);
|
||||
private static final PartParser MONTH_VALUE_PARSER = PartParser.of(Part.MONTH);
|
||||
private static final PartParser DAY_OF_WEEK_VALUE_PARSER = PartParser.of(Part.DAY_OF_WEEK);
|
||||
private static final PartParser YEAR_VALUE_PARSER = PartParser.of(Part.YEAR);
|
||||
|
||||
/**
|
||||
* 解析表达式到匹配表中
|
||||
@@ -46,7 +47,7 @@ public class CronPatternParser {
|
||||
* @return {@link MatcherTable}
|
||||
*/
|
||||
private static MatcherTable parseGroupPattern(String groupPattern) {
|
||||
final List<String> patternList = StrUtil.split(groupPattern, '|');
|
||||
final List<String> patternList = StrUtil.splitTrim(groupPattern, '|');
|
||||
final MatcherTable matcherTable = new MatcherTable(patternList.size());
|
||||
for (String pattern : patternList) {
|
||||
matcherTable.matchers.add(parseSinglePattern(pattern));
|
||||
@@ -61,17 +62,18 @@ public class CronPatternParser {
|
||||
* @return {@link DateTimeMatcher}
|
||||
*/
|
||||
private static DateTimeMatcher parseSinglePattern(String pattern) {
|
||||
final String[] parts = pattern.split("\\s");
|
||||
final String[] parts = pattern.split("\\s+");
|
||||
Assert.checkBetween(parts.length, 5, 7,
|
||||
() -> new CronException("Pattern [{}] is invalid, it must be 5-7 parts!", pattern));
|
||||
|
||||
int offset = 0;// 偏移量用于兼容Quartz表达式,当表达式有6或7项时,第一项为秒
|
||||
// 偏移量用于兼容Quartz表达式,当表达式有6或7项时,第一项为秒
|
||||
int offset = 0;
|
||||
if (parts.length == 6 || parts.length == 7) {
|
||||
offset = 1;
|
||||
} else if (parts.length != 5) {
|
||||
throw new CronException("Pattern [{}] is invalid, it must be 5-7 parts!", pattern);
|
||||
}
|
||||
|
||||
// 秒,如果不支持秒的表达式,则第一位按照表达式生成时间的秒数赋值,表示整分匹配
|
||||
final String secondPart = (1 == offset) ? parts[0] : String.valueOf(DateUtil.date().second());
|
||||
// 秒,如果不支持秒的表达式,则第一位赋值0,表示整分匹配
|
||||
final String secondPart = (1 == offset) ? parts[0] : "0";
|
||||
|
||||
// 年
|
||||
ValueMatcher yearMatcher;
|
@@ -1,10 +0,0 @@
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
/**
|
||||
* 秒值处理<br>
|
||||
* 限定于0-59
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class SecondValueParser extends MinuteValueParser {
|
||||
}
|
@@ -1,53 +0,0 @@
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
import cn.hutool.cron.pattern.matcher.ValueMatcher;
|
||||
|
||||
/**
|
||||
* 值处理接口<br>
|
||||
* 值处理用于限定表达式中相应位置的值范围,并转换表达式值为int值
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public interface ValueParser {
|
||||
|
||||
/**
|
||||
* 解析表达式对应部分为{@link ValueMatcher},支持的表达式包括:
|
||||
* <ul>
|
||||
* <li>单值或通配符形式,如 <strong>a</strong> 或 <strong>*</strong></li>
|
||||
* <li>数组形式,如 <strong>1,2,3</strong></li>
|
||||
* <li>间隔形式,如 <strong>a/b</strong> 或 <strong>*/b</strong></li>
|
||||
* <li>范围形式,如 <strong>3-8</strong></li>
|
||||
* </ul>
|
||||
*
|
||||
* @param pattern 对应时间部分的表达式
|
||||
* @return {@link ValueMatcher}
|
||||
*/
|
||||
ValueMatcher parseAsValueMatcher(String pattern);
|
||||
|
||||
/**
|
||||
* 处理String值并转为int<br>
|
||||
* 转换包括:
|
||||
* <ol>
|
||||
* <li>数字字符串转为数字</li>
|
||||
* <li>别名转为对应的数字(如月份和星期)</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param value String值
|
||||
* @return int
|
||||
*/
|
||||
int parse(String value);
|
||||
|
||||
/**
|
||||
* 返回最小值
|
||||
*
|
||||
* @return 最小值
|
||||
*/
|
||||
int getMin();
|
||||
|
||||
/**
|
||||
* 返回最大值
|
||||
*
|
||||
* @return 最大值
|
||||
*/
|
||||
int getMax();
|
||||
}
|
@@ -1,25 +0,0 @@
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
import cn.hutool.cron.pattern.matcher.ValueMatcher;
|
||||
import cn.hutool.cron.pattern.matcher.YearValueMatcher;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 年值处理<br>
|
||||
* 年的限定在1970-2099年
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class YearValueParser extends AbsValueParser {
|
||||
|
||||
public YearValueParser() {
|
||||
super(1970, 2099);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ValueMatcher buildValueMatcher(List<Integer> values) {
|
||||
//考虑年数字太大,不适合boolean数组,单独使用列表遍历匹配
|
||||
return new YearValueMatcher(values);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user