/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.xinhui.cronhelper;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.TimeZone;
import org.springframework.util.StringUtils;
/**
* 定时器Cron解析帮助类,提供获取下一次执行时间
*
* <p>
* 示例:
* <ul>
* <li>"0 0 * * * *" = 每天每时0分0秒执行</li>
* <li>"0 0 8-10 * * *" = 每天8点9点10点0分0秒执行</li>
* <li>"0 0 6,19 * * *" = 每天6点19点0分0秒执行</li>
* <li>"0 0/30 8-10 * * *" = 每天的8点到10点每30分钟执行</li>
* <li>"0 0 9-17 * * MON-FRI" = 周末的9点到17点每小时执行</li>
* <li>"0 0 0 25 12 ?" = 每年的12月25日0分0秒执行</li>
* </ul>
*
*/
public class CronHelper {
private final String expression;
private final TimeZone timeZone;
private final BitSet months = new BitSet(12);
private final BitSet daysOfMonth = new BitSet(31);
private final BitSet daysOfWeek = new BitSet(7);
private final BitSet hours = new BitSet(24);
private final BitSet minutes = new BitSet(60);
private final BitSet seconds = new BitSet(60);
/**
* 默认以当前系统所在的时区进行解析(本地为东8区时间,即北京时间)
*
* @param expression
* 以空格分隔的时间字段表达式
*/
public CronHelper(String expression) {
this(expression, TimeZone.getDefault());
}
/**
* @param expression
* 以空格分隔的时间字段表达式
* @param timeZone
* 指定的时区
*/
public CronHelper(String expression, TimeZone timeZone) {
this.expression = expression;
this.timeZone = timeZone;
parse(expression);
}
private CronHelper(String expression, String[] fields) {
this.expression = expression;
this.timeZone = null;
doParse(fields);
}
/**
*
* @return 获取Cron表达式
*/
String getExpression() {
return this.expression;
}
/**
* 获取一个给定时间之后的,匹配cron的时间
*
* @param date
* @return cron下一次执行的时间
*/
public Date next(Date date) {
/*
* 步骤:
*
* 1 从整秒开始(必要时毫秒进行四舍五入)
*
* 2 如果秒匹配则继续, 否则找下一个匹配:
* 2.1 如果下一个匹配在下一分钟进行,则向前滚动
*
* 3 如果分钟还是匹配的则继续进行, 否则找下一个匹配
* 3.1 如果下一个匹配在下一个小时,则向前滚
* 3.2 重置秒,转向步骤2
*
* 4 如果小时还是匹配则继续进行, 否则找下一个匹配
* 4.1 如果下一个匹配在下一天,则向前滚,
* 4.2 重置分钟和秒,转向步骤2
*/
Calendar calendar = new GregorianCalendar();
calendar.setTimeZone(this.timeZone);
calendar.setTime(date);
// 重置清零毫秒数
calendar.set(Calendar.MILLISECOND, 0);
long originalTimestamp = calendar.getTimeInMillis();
doNext(calendar, calendar.get(Calendar.YEAR));
if (calendar.getTimeInMillis() == originalTimestamp) {
// 若当前时间即是触发时间 - 将时间调整到下一整秒,然后再试一次...
calendar.add(Calendar.SECOND, 1);
doNext(calendar, calendar.get(Calendar.YEAR));
}
return calendar.getTime();
}
/**
*
* @param calendar
* 日历
* @param dot
* 年份
*/
private void doNext(Calendar calendar, int dot) {
List<Integer> resets = new ArrayList<Integer>();
// 当前秒
int second = calendar.get(Calendar.SECOND);
List<Integer> emptyList = Collections.emptyList();
int updateSecond = findNext(this.seconds, second, calendar, Calendar.SECOND, Calendar.MINUTE, emptyList);
if (second == updateSecond) {
resets.add(Calendar.SECOND);
}
// 当前分钟
int minute = calendar.get(Calendar.MINUTE);
int updateMinute = findNext(this.minutes, minute, calendar, Calendar.MINUTE, Calendar.HOUR_OF_DAY, resets);
if (minute == updateMinute) {
resets.add(Calendar.MINUTE);
} else {
doNext(calendar, dot);
}
// 当前小时
int hour = calendar.get(Calendar.HOUR_OF_DAY);
int updateHour = findNext(this.hours, hour, calendar, Calendar.HOUR_OF_DAY, Calendar.DAY_OF_WEEK, resets);
if (hour == updateHour) {
resets.add(Calendar.HOUR_OF_DAY);
} else {
doNext(calendar, dot);
}
// 当前是一周的第几天
int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
// 当前是一月的第几天
int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
int updateDayOfMonth = findNextDay(calendar, this.daysOfMonth, dayOfMonth, daysOfWeek, dayOfWeek, resets);
if (dayOfMonth == updateDayOfMonth) {
resets.add(Calendar.DAY_OF_MONTH);
} else {
doNext(calendar, dot);
}
// 当前月
int month = calendar.get(Calendar.MONTH);
int updateMonth = findNext(this.months, month, calendar, Calendar.MONTH, Calendar.YEAR, resets);
if (month != updateMonth) {
if (calendar.get(Calendar.YEAR) - dot > 4) {//4年一瑞年,年份大于4年一周期都没找到为无效月份
throw new IllegalArgumentException("CRON表达式 \"" + this.expression + "\"中,无效的月份导致错误");
}
doNext(calendar, dot);
}
}
private int findNextDay(Calendar calendar, BitSet daysOfMonth, int dayOfMonth, BitSet daysOfWeek, int dayOfWeek, List<Integer> resets) {
int count = 0;
int max = 366;
// DAY_OF_WEEK 的值在Calendar中从1 (Sunday)开始。但是在CRON表达式中从0开始,所以在这减去1
while ((!daysOfMonth.get(dayOfMonth) || !daysOfWeek.get(dayOfWeek - 1)) && count++ < max) {
calendar.add(Calendar.DAY_OF_MONTH, 1);
dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
reset(calendar, resets);
}
if (count >= max) {
throw new IllegalArgumentException("表达式 \"" + this.expression + "\"中的天数超出366天范围");
}
return dayOfMonth;
}
/**
* 在提供的bits集合中搜索对应的值之后的下一个设置位的值,并重置Calendar。
*
* @param bits
* 字段的允许值
* @param value
* 当前值
* @param calendar
* Calendar按照bits集合滚动增加
* @param field
* Calendar字段的增加(按照定义有效字段的静态常量值)
* @param lowerOrders
* 要重置的Calendar字段
* @return 下一个在calendar中的值
*/
private int findNext(BitSet bits, int value, Calendar calendar, int field, int nextField, List<Integer> lowerOrders) {
int nextValue = bits.nextSetBit(value);
// 越界下一个字段加1,继续从头开始查找
if (nextValue == -1) {
calendar.add(nextField, 1);
reset(calendar, Collections.singletonList(field));
nextValue = bits.nextSetBit(0);
}
if (nextValue != value) {
calendar.set(field, nextValue);
reset(calendar, lowerOrders);
}
return nextValue;
}
/**
* 将
Cron表达式解析类和时间相关操作工具类
182 浏览量
2024-05-20
09:55:30
上传
评论
收藏 8KB RAR 举报
芯晖闲云
- 粉丝: 23
- 资源: 21
最新资源
- 示例代码:java动态代理和cglib代理的简单例子
- 应急响应-linux入侵排查.md
- 基于Springboot的漫画网站(有报告) Javaee项目,springboot项目
- 年金、净现值NPV、IRR、现值PV、终值FV、EAR等常见概念.pdf
- 数据处理matlab代码
- 小程序版基于深度学习对火龙果成熟度识别-不含数据集图片-含逐行注释和说明文档.zip
- 小程序版CNN图像分类识别牛油果是否腐烂-不含数据集图片-含逐行注释和说明文档.zip
- 小程序版深度学习CNN训练识别食物新鲜-不含数据集图片-含逐行注释和说明文档.zip
- 基于SSM的大学学生成长系统(有报告) Javaee项目 ssm项目
- 小程序版通过CNN卷积神经网络的手指静脉识别-不含数据集图片-含逐行注释和说明文档.zip
资源上传下载、课程学习等过程中有任何疑问或建议,欢迎提出宝贵意见哦~我们会及时处理!
点击此处反馈