All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.dromara.hutool.cron.pattern.matcher.PatternMatcher Maven / Gradle / Ivy

There is a newer version: 6.0.0.M3
Show newest version
/*
 * Copyright (c) 2023 looly([email protected])
 * Hutool is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          https://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

package org.dromara.hutool.cron.pattern.matcher;

import org.dromara.hutool.core.date.DateUtil;
import org.dromara.hutool.cron.pattern.Part;

import java.time.Year;
import java.util.Calendar;
import java.util.TimeZone;

/**
 * 单一表达式的匹配器,匹配器由7个{@link PartMatcher}组成,分别是:
 * 
 *         0      1     2        3         4       5        6
 *      SECOND MINUTE HOUR DAY_OF_MONTH MONTH DAY_OF_WEEK YEAR
 * 
* * @author looly * @since 5.8.0 */ public class PatternMatcher { private final PartMatcher[] matchers; /** * 构造 * * @param secondMatcher 秒匹配器 * @param minuteMatcher 分匹配器 * @param hourMatcher 时匹配器 * @param dayOfMonthMatcher 日匹配器 * @param monthMatcher 月匹配器 * @param dayOfWeekMatcher 周匹配器 * @param yearMatcher 年匹配器 */ public PatternMatcher(final PartMatcher secondMatcher, final PartMatcher minuteMatcher, final PartMatcher hourMatcher, final PartMatcher dayOfMonthMatcher, final PartMatcher monthMatcher, final PartMatcher dayOfWeekMatcher, final PartMatcher yearMatcher) { matchers = new PartMatcher[]{ secondMatcher, minuteMatcher, hourMatcher, dayOfMonthMatcher, monthMatcher, dayOfWeekMatcher, yearMatcher }; } /** * 根据表达式位置,获取对应的{@link PartMatcher} * * @param part 表达式位置 * @return {@link PartMatcher} */ public PartMatcher get(final Part part) { return matchers[part.ordinal()]; } //region match /** * 给定时间是否匹配定时任务表达式 * * @param fields 时间字段值,{second, minute, hour, dayOfMonth, month, dayOfWeek, year} * @return 如果匹配返回 {@code true}, 否则返回 {@code false} */ public boolean match(final int[] fields) { return match(fields[0], fields[1], fields[2], fields[3], fields[4], fields[5], fields[6]); } /** * 给定周的值是否匹配定时任务表达式对应部分 * * @param dayOfWeekValue dayOfMonth值,星期从0开始,0和7都表示周日 * @return 如果匹配返回 {@code true}, 否则返回 {@code false} * @since 5.8.0 */ public boolean matchWeek(final int dayOfWeekValue) { return matchers[5].test(dayOfWeekValue); } /** * 给定时间是否匹配定时任务表达式 * * @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(final int second, final int minute, final int hour, final int dayOfMonth, final int month, final int dayOfWeek, final int year) { return ((second < 0) || matchers[0].test(second)) // 匹配秒(非秒匹配模式下始终返回true) && matchers[1].test(minute)// 匹配分 && matchers[2].test(hour)// 匹配时 && matchDayOfMonth(matchers[3], dayOfMonth, month, Year.isLeap(year))// 匹配日 && matchers[4].test(month) // 匹配月 && matchers[5].test(dayOfWeek)// 匹配周 && matchers[6].test(year);// 匹配年 } /** * 是否匹配日(指定月份的第几天) * * @param matcher {@link PartMatcher} * @param dayOfMonth 日 * @param month 月 * @param isLeapYear 是否闰年 * @return 是否匹配 */ private static boolean matchDayOfMonth(final PartMatcher matcher, final int dayOfMonth, final int month, final boolean isLeapYear) { return ((matcher instanceof DayOfMonthMatcher) // ? ((DayOfMonthMatcher) matcher).match(dayOfMonth, month, isLeapYear) // : matcher.test(dayOfMonth)); } //endregion //region nextMatchAfter /** * 获取下一个匹配日期时间
* 获取方法是,先从年开始查找对应部分的下一个值: *
    *
  • 如果此部分下个值不变,获取下一个部分
  • *
  • 如果此部分下个值大于给定值,以下所有值置为最小值
  • *
  • 如果此部分下个值小于给定值,回退到上一个值获取下一个新值,之后的值置为最小值
  • *
* *
	 *        秒 分 时 日 月(1) 周(0) 年
	 *     下 <-----------------> 上
	 * 
* * @param values 时间字段值,{second, minute, hour, dayOfMonth, monthBase1, dayOfWeekBase0, year}, * 注意这个字段值会被修改 * @param zone 时区 * @return {@link Calendar},毫秒数为0 */ public Calendar nextMatchAfter(final int[] values, final TimeZone zone) { final Calendar calendar = Calendar.getInstance(zone); calendar.set(Calendar.MILLISECOND, 0); final int[] newValues = nextMatchValuesAfter(values); for (int i = 0; i < newValues.length; i++) { // 周无需设置 if (i != Part.DAY_OF_WEEK.ordinal()) { setValue(calendar, Part.of(i), newValues[i]); } } return calendar; } /** * 获取下一个匹配日期时间
* 获取方法是,先从年开始查找对应部分的下一个值: *
    *
  • 如果此部分下个值不变,获取下一个部分
  • *
  • 如果此部分下个值大于给定值,以下所有值置为最小值
  • *
  • 如果此部分下个值小于给定值,回退到上一个值获取下一个新值,之后的值置为最小值
  • *
* *
{@code
	 *          秒 分 时 日 月 周 年
	 *     下 <--------------------> 上
	 * }
* * @param values 时间字段值,{second, minute, hour, dayOfMonth, monthBase1, dayOfWeekBase0, year} * @return {@link Calendar},毫秒数为0 */ private int[] nextMatchValuesAfter(final int[] values) { int i = Part.YEAR.ordinal(); // 新值,-1表示标识为回退 int nextValue = 0; while (i >= 0) { if (i == Part.DAY_OF_WEEK.ordinal()) { // 周不参与计算 i--; continue; } nextValue = getNextMatch(values, i, 0); if (nextValue > values[i]) { // 此部分正常获取新值,结束循环,后续的部分置最小值 values[i] = nextValue; i--; break; } else if (nextValue < values[i]) { // 此部分下一个值获取到的值产生回退,回到上一个部分,继续获取新值 i++; nextValue = -1;// 标记回退查找 break; } // 值不变,检查下一个部分 i--; } // 值产生回退,向上查找变更值 if (-1 == nextValue) { while (i <= Part.YEAR.ordinal()) { if (i == Part.DAY_OF_WEEK.ordinal()) { // 周不参与计算 i++; continue; } nextValue = getNextMatch(values, i, 1); if (nextValue > values[i]) { values[i] = nextValue; i--; break; } i++; } } // 修改值以下的字段全部归最小值 setToMin(values, i); return values; } /** * 获取指定部分的下一个匹配值,三种结果: *
    *
  • 结果值大于原值:此部分已更新,后续部分取匹配的最小值。
  • *
  • 结果值小于原值:此部分获取到了最小值,上一个部分需要继续取下一个值。
  • *
  • 结果值等于原值:此部分匹配,获取下一个部分的next值
  • *
* * @param newValues 时间字段值,{second, minute, hour, dayOfMonth, monthBase1, dayOfWeekBase0, year} * @param partOrdinal 序号 * @return 下一个值 */ private int getNextMatch(final int[] newValues, final int partOrdinal, final int plusValue) { if (partOrdinal == Part.DAY_OF_MONTH.ordinal() && matchers[partOrdinal] instanceof DayOfMonthMatcher) { final boolean isLeapYear = DateUtil.isLeapYear(newValues[Part.YEAR.ordinal()]); final int month = newValues[Part.MONTH.ordinal()]; return ((DayOfMonthMatcher) matchers[partOrdinal]).nextAfter(newValues[partOrdinal] + plusValue, month, isLeapYear); } return matchers[partOrdinal].nextAfter(newValues[partOrdinal] + plusValue); } /** * 设置从{@link Part#SECOND}到指定部分,全部设置为最小值 * * @param values 值数组 * @param toPart 截止的部分 */ private void setToMin(final int[] values, final int toPart) { Part part; for (int i = toPart; i >= 0; i--) { part = Part.of(i); if (part == Part.DAY_OF_MONTH) { final boolean isLeapYear = DateUtil.isLeapYear(values[Part.YEAR.ordinal()]); final int month = values[Part.MONTH.ordinal()]; final PartMatcher partMatcher = get(part); if (partMatcher instanceof DayOfMonthMatcher) { values[i] = ((DayOfMonthMatcher) partMatcher).getMinValue(month, isLeapYear); continue; } } values[i] = getMin(part); } } /** * 获取表达式部分的最小值 * * @param part {@link Part} * @return 最小值,如果匹配所有,返回对应部分范围的最小值 */ private int getMin(final Part part) { final PartMatcher matcher = get(part); final 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 /** * 设置对应部分修正后的值
*
    *
  • 月在表达式中从1开始,但是{@link Calendar}中是从0开始的,需要-1
  • *
  • 周在表达式中从0开始(0表示周日),但是{@link Calendar}中是从1开始的(1表示周日),需要+1
  • *
* * @param calendar {@link Calendar} * @param part 表达式部分 * @param value 值 * @return {@link Calendar} */ private Calendar setValue(final Calendar calendar, final 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); //Console.log("Set [{}] as [{}]", part, value); return calendar; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy