diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java index 80e6e1d26..c0190f1d1 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java @@ -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 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 nextMatchs = new ArrayList<>(second); + for (PatternMatcher matcher : matchers) { + nextMatchs.add(matcher.nextMatchAfter( + second, minute, hour, dayOfMonth, month, dayOfWeek, year, zone)); + } + // 返回匹配到的最早日期 + return CollUtil.min(nextMatchs); + } } diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/AlwaysTrueValueMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/AlwaysTrueMatcher.java similarity index 72% rename from hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/AlwaysTrueValueMatcher.java rename to hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/AlwaysTrueMatcher.java index 54b8ce7c5..6d65fd88b 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/AlwaysTrueValueMatcher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/AlwaysTrueMatcher.java @@ -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) { diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/BoolArrayValueMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/BoolArrayMatcher.java similarity index 92% rename from hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/BoolArrayValueMatcher.java rename to hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/BoolArrayMatcher.java index 1263ad168..d60909ec5 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/BoolArrayValueMatcher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/BoolArrayMatcher.java @@ -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 intValueList) { + public BoolArrayMatcher(List intValueList) { Assert.isTrue(CollUtil.isNotEmpty(intValueList), "Values must be not empty!"); bValues = new boolean[Collections.max(intValueList) + 1]; int min = Integer.MAX_VALUE; diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DateTimeMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DateTimeMatcher.java deleted file mode 100644 index 55975cbde..000000000 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DateTimeMatcher.java +++ /dev/null @@ -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)); - } -} diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthMatcher.java similarity index 91% rename from hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java rename to hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthMatcher.java index 7969871b7..61a395d77 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthMatcher.java @@ -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 intValueList) { + public DayOfMonthMatcher(List intValueList) { super(intValueList); } diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/MatcherTable.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/MatcherTable.java deleted file mode 100644 index 87a09d9c5..000000000 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/MatcherTable.java +++ /dev/null @@ -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 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 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; - } -} diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/ValueMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/PartMatcher.java similarity index 76% rename from hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/ValueMatcher.java rename to hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/PartMatcher.java index 8e501eaa3..e62e28417 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/ValueMatcher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/PartMatcher.java @@ -3,12 +3,12 @@ package cn.hutool.cron.pattern.matcher; import cn.hutool.core.lang.Matcher; /** - * 值匹配器
+ * 表达式中的某个位置部分匹配器
* 用于匹配日期位中对应数字是否匹配 * * @author Looly */ -public interface ValueMatcher extends Matcher { +public interface PartMatcher extends Matcher { /** * 获取指定值之后的匹配值,也可以是指定值本身 * @param value 指定的值 diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/PatternMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/PatternMatcher.java new file mode 100644 index 000000000..1cd364e38 --- /dev/null +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/PatternMatcher.java @@ -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 +} diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/YearValueMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/YearValueMatcher.java index 58bc6a7da..2f3abc8d8 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/YearValueMatcher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/YearValueMatcher.java @@ -9,7 +9,7 @@ import java.util.LinkedHashSet; * * @author Looly */ -public class YearValueMatcher implements ValueMatcher { +public class YearValueMatcher implements PartMatcher { private final LinkedHashSet valueList; diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/PartParser.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/PartParser.java index 6a2341abd..05cb0d350 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/PartParser.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/PartParser.java @@ -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}
+ * 定时任务表达式各个部分的解析器,根据{@link Part}指定不同部分,解析为{@link PartMatcher}
* 每个部分支持: *
    *
  • * :表示匹配这个位置所有的时间
  • @@ -56,21 +56,21 @@ public class PartParser { } /** - * 将表达式解析为{@link ValueMatcher}
    + * 将表达式解析为{@link PartMatcher}
    *
      - *
    • * 或者 ? 返回{@link AlwaysTrueValueMatcher}
    • - *
    • {@link Part#DAY_OF_MONTH} 返回{@link DayOfMonthValueMatcher}
    • + *
    • * 或者 ? 返回{@link AlwaysTrueMatcher}
    • + *
    • {@link Part#DAY_OF_MONTH} 返回{@link DayOfMonthMatcher}
    • *
    • {@link Part#YEAR} 返回{@link YearValueMatcher}
    • - *
    • 其他 返回{@link BoolArrayValueMatcher}
    • + *
    • 其他 返回{@link BoolArrayMatcher}
    • *
    * * @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 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(); diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/PatternParser.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/PatternParser.java index fedfc094f..0b95e30f0 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/PatternParser.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/PatternParser.java @@ -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 parse(String cronPattern) { return parseGroupPattern(cronPattern); } @@ -44,24 +44,24 @@ public class PatternParser { * * * @param groupPattern 复合表达式 - * @return {@link MatcherTable} + * @return {@link List} */ - private static MatcherTable parseGroupPattern(String groupPattern) { + private static List parseGroupPattern(String groupPattern) { final List patternList = StrUtil.splitTrim(groupPattern, '|'); - final MatcherTable matcherTable = new MatcherTable(patternList.size()); + final List 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 ); diff --git a/hutool-cron/src/test/java/cn/hutool/cron/pattern/CronPatternNextMatchTest.java b/hutool-cron/src/test/java/cn/hutool/cron/pattern/CronPatternNextMatchTest.java index dcc53b8fe..6ee3bf0e8 100644 --- a/hutool-cron/src/test/java/cn/hutool/cron/pattern/CronPatternNextMatchTest.java +++ b/hutool-cron/src/test/java/cn/hutool/cron/pattern/CronPatternNextMatchTest.java @@ -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)); } } diff --git a/hutool-cron/src/test/java/cn/hutool/cron/pattern/CronPatternTest.java b/hutool-cron/src/test/java/cn/hutool/cron/pattern/CronPatternTest.java index 51e34921a..c9c63130c 100644 --- a/hutool-cron/src/test/java/cn/hutool/cron/pattern/CronPatternTest.java +++ b/hutool-cron/src/test/java/cn/hutool/cron/pattern/CronPatternTest.java @@ -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 * * * ?");