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 c0190f1d1..f4c8ff431 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 @@ -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 nextMatchs = new ArrayList<>(second); + private Calendar nextMatchAfter(int[] values, TimeZone zone) { + final List 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); } } diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/Part.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/Part.java index fcbf0b4a6..aa7ee5c3d 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/Part.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/Part.java @@ -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; + /** * 表达式各个部分的枚举,用于限定在表达式中的位置和规则(如最小值和最大值)
* {@link #ordinal()}表示此部分在表达式中的位置,如0表示秒
@@ -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]; + } } 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 index ac3355b45..ce4f63e85 100644 --- 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 @@ -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; + } } 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 c3a52a1ea..a0a53e9e5 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 @@ -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()); } }