参见 http://gitee.com/xxssyyyyssxx/cron-hms
书接上一篇,根据cron表达式切割计算每个域的执行点,可以计算出给定时间的下一个执行时间。思路是:
1、找到所有时分秒的组合并按照时分秒排序
2、给定的时分秒在以上集合之前、之后处理
3、给定时时分秒在以上集合中找到一个最小的位置
4、day+1循环直到找到满足月、星期的那一天
public class CronUtil {
private static final int CRON_LEN = 6;
private static final int CRON_LEN_YEAR = 7;
private static final String CRON_CUT = "\\s+";
private static final int MAX_ADD_COUNT = 365;
private static final int MAX_ADD_YEAR = 10;
/**
* 给定cron表达式和日期,计算满足cron的下一个执行时间点
*
* @param cron cron表达式
* @param date 日期时间
* @return 满足cron的下一个执行时间点
*/
public static Date next(String cron, Date date) {
List cronFields = convertCronField(cron);
CronField fieldSecond = cronFields.get(CronPosition.SECOND.getPosition());
CronField fieldMinute = cronFields.get(CronPosition.MINUTE.getPosition());
CronField fieldHour = cronFields.get(CronPosition.HOUR.getPosition());
CronField fieldDay = cronFields.get(CronPosition.DAY.getPosition());
CronField fieldMonth = cronFields.get(CronPosition.MONTH.getPosition());
CronField fieldWeek = cronFields.get(CronPosition.WEEK.getPosition());
Calendar calendar = Calendar.getInstance();
//基准线,至少从下一秒开始
calendar.setTime(date);
calendar.add(Calendar.SECOND , 1);
/// 如果包含年域
if (CRON_LEN_YEAR == cronFields.size()) {
Integer year = DateUtil.year(date);
CronField fieldYear = cronFields.get(CronPosition.YEAR.getPosition());
List listYear = fieldYear.points();
Integer calYear = CompareUtil.findNext(year, listYear);
if (!year.equals(calYear)) {
calendar.set(Calendar.YEAR, calYear);
}
}
return doNext(0, calendar, fieldSecond, fieldMinute, fieldHour, fieldDay, fieldMonth, fieldWeek);
}
private static Date doNext(int addYear , Calendar calendar, CronField fieldSecond, CronField fieldMinute, CronField fieldHour, CronField fieldDay, CronField fieldMonth, CronField fieldWeek) {
if(addYear >= MAX_ADD_YEAR){
throw new IllegalArgumentException("Invalid cron expression \"" +
(fieldSecond.getExpress() + " "
+ fieldMinute.getExpress() + " "
+ fieldHour.getExpress() + " "
+ fieldDay.getExpress() + " "
+ fieldMonth.getExpress() + " "
+ fieldWeek.getExpress())
+ "\" which led to runaway search for next trigger");
}
Date newDate = calendar.getTime();
//先确定时分秒
Integer hourNow = DateUtil.hour(newDate);
Integer minuteNow = DateUtil.minute(newDate);
Integer secondNow = DateUtil.second(newDate);
List listHour = fieldHour.points();
List listMinute = fieldMinute.points();
List listSecond = fieldSecond.points();
//找到所有时分秒的组合
List points = new ArrayList<>(listHour.size() * listMinute.size() * listSecond.size());
for (Integer hour : listHour) {
for (Integer minute : listMinute) {
for (Integer second : listSecond) {
points.add(new TimeOfDay(hour, minute, second));
}
}
}
//排序
Collections.sort(points);
TimeOfDay timeOfDayNow = new TimeOfDay(hourNow, minuteNow, secondNow);
//小于最小的
TimeOfDay timeOfDayMin = points.get(0);
TimeOfDay timeOfDayMax = points.get(points.size() - 1);
if (timeOfDayNow.compareTo(timeOfDayMin) < 0) {
setHMS(calendar, timeOfDayMin);
//大于最大的
} else if (timeOfDayNow.compareTo(timeOfDayMax) > 0) {
setHMS(calendar, timeOfDayMin);
calendar.add(Calendar.DAY_OF_MONTH, 1);
} else {
///
/*for (TimeOfDay point : points) {
//从小到大的列表中找到第一个大于等于某个值的
if(timeOfDayNow.compareTo(point) <= 0){
setHMS(calendar , point);
break;
}
}*/
TimeOfDay next = CompareUtil.findNext(timeOfDayNow, points);
setHMS(calendar, next);
}
Date tmp = calendar.getTime();
Integer day = DateUtil.day(tmp);
Integer month = DateUtil.month(tmp);
Integer week = DateUtil.week(tmp);
///天、月、周必须都满足,否则加一天
int count = 0;
boolean setting = false;
while (!satisfy(day, fieldDay)
|| !satisfy(month , fieldMonth)
|| !satisfy(week , fieldWeek) ) {
calendar.add(Calendar.DAY_OF_MONTH, 1);
Date t = calendar.getTime();
day = DateUtil.day(t);
month = DateUtil.month(t);
week = DateUtil.week(t);
//加了一天的情况下,时分秒就可以用最小的了,只需要设置一次
if (!setting) {
setHMS(calendar, timeOfDayMin);
setting = true;
}
count++;
//极端情况下:这尼玛太坑了,一般遇不到:加了一年还未找到
if (count >= MAX_ADD_COUNT) {
break;
//throw new IllegalArgumentException("一年之中都未找到符合要求的时间,请检查您的cron表达式");
}
}
///其实可以再一天天往下找直到找到为止
if(count >= MAX_ADD_COUNT){
return doNext(++addYear , calendar, fieldSecond, fieldMinute, fieldHour, fieldDay, fieldMonth, fieldWeek);
}
return calendar.getTime();
}
/**
* 给定一个值,看是否满足cron表达示
* @param fieldValue 给定值
* @param field 域
* @return *或者值在集合中
*/
private static boolean satisfy(Integer fieldValue, CronField field) {
//利用 || 的短路特性可以避免 points 计算 , 并且 points本身是有缓存的
return field.containsAll() || CompareUtil.inList(fieldValue, field.points());
}
/**
* 设置时分秒域
*/
private static void setHMS(Calendar calendar, TimeOfDay timeOfDay) {
calendar.set(Calendar.HOUR_OF_DAY, timeOfDay.getHour());
calendar.set(Calendar.MINUTE, timeOfDay.getMinute());
calendar.set(Calendar.SECOND, timeOfDay.getSecond());
}
}
除开解析L、W、C等,至此完成。
https://blog.csdn.net/weixin_40426638/article/details/78959972
https://blog.csdn.net/dabing69221/article/details/19012581
https://blog.csdn.net/ukulelepku/article/details/54310035
项目地址见:
https://gitee.com/xxssyyyyssxx/cron-hms
单元测试:(跟Spring提供的CronSequenceGenerator比较才知道我这个算法仅仅作为一种学习cron的思考,实现还比较粗糙,差距还比较大)
package top.jfunc.cron;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import top.jfunc.cron.util.CronSequenceGenerator;
import top.jfunc.cron.util.CronUtil;
import top.jfunc.cron.util.DateUtil;
import java.util.Date;
/**
* @author xiongshiyan at 2018/11/18 , contact me with email [email protected] or phone 15208384257
*/
public class CronNextTest {
@Test
public void testNext1(){
Date date = DateUtil.toDate("2018-11-18 12:00:12");
String cron = "2 15 12 ? * *";
Date next = new CronSequenceGenerator(cron).next(date);
Assert.assertEquals("2018-11-18 12:15:02" , DateUtil.toStr(next));
Date next1 = CronUtil.next(cron, date);
Assert.assertEquals("2018-11-18 12:15:02" , DateUtil.toStr(next1));
}
@Test
public void testNext2(){
Date date = DateUtil.toDate("2011-03-25 13:22:43");
String cron = "0 0 8 * * *";
Date next = new CronSequenceGenerator(cron).next(date);
Assert.assertEquals("2011-03-26 08:00:00" , DateUtil.toStr(next));
Date next1 = CronUtil.next(cron, date);
Assert.assertEquals("2011-03-26 08:00:00" , DateUtil.toStr(next1));
}
@Test
public void testNext3(){
Date date = DateUtil.toDate("2016-12-25 18:00:45");
String cron = "0/2 1 * * * *";
Date next = new CronSequenceGenerator(cron).next(date);
Assert.assertEquals("2016-12-25 18:01:46" , DateUtil.toStr(next));
Date next1 = CronUtil.next(cron, date);
//18:01:46??? // 需要每个域大于当前的???
Assert.assertEquals("2016-12-25 18:01:00" , DateUtil.toStr(next1));
}
@Test
public void testNext4(){
Date date = DateUtil.toDate("2016-01-29 04:01:12");
String cron = "0 0/5 14,18 * * ?";
Date next = new CronSequenceGenerator(cron).next(date);
Assert.assertEquals("2016-01-29 14:00:00" , DateUtil.toStr(next));
Date next1 = CronUtil.next(cron, date);
Assert.assertEquals("2016-01-29 14:00:00" , DateUtil.toStr(next1));
}
@Test
public void testNext5(){
Date date = DateUtil.toDate("2022-08-31 23:59:59");
String cron = "0 15 10 ? * MON-FRI";
Date next = new CronSequenceGenerator(cron).next(date);
Assert.assertEquals("2022-09-01 10:15:00" , DateUtil.toStr(next));
Date next1 = CronUtil.next(cron, date);
Assert.assertEquals("2022-09-01 10:15:00" , DateUtil.toStr(next1));
}
@Test
public void testNext6(){
Date date = DateUtil.toDate("2013-09-12 03:04:05");
String cron = "0 26,29,33 * * * ?";
Date next = new CronSequenceGenerator(cron).next(date);
Assert.assertEquals("2013-09-12 03:26:00" , DateUtil.toStr(next));
Date next1 = CronUtil.next(cron, date);
Assert.assertEquals("2013-09-12 03:26:00" , DateUtil.toStr(next1));
}
@Test
public void testNext7(){
Date date = DateUtil.toDate("1999-10-18 12:00:00");
String cron = "10-20/4 10,44,30/2 10 ? 3 WED";
Date next = new CronSequenceGenerator(cron).next(date);
Assert.assertEquals("2000-03-01 10:10:10" , DateUtil.toStr(next));
Date next1 = CronUtil.next(cron, date);
Assert.assertEquals("2000-03-01 10:10:10" , DateUtil.toStr(next1));
date = DateUtil.toDate("2018-11-20 12:00:00");
cron = "10 10 12 ? * 2";
next = new CronSequenceGenerator(cron).next(date);
Assert.assertEquals("2018-11-20 12:10:10" , DateUtil.toStr(next));
next1 = CronUtil.next(cron, date);
Assert.assertEquals("2018-11-20 12:10:10" , DateUtil.toStr(next1));
cron = "10 10 12 ? * 0";
next = new CronSequenceGenerator(cron).next(date);
Assert.assertEquals("2018-11-25 12:10:10" , DateUtil.toStr(next));
next1 = CronUtil.next(cron, date);
Assert.assertEquals("2018-11-25 12:10:10" , DateUtil.toStr(next1));
}
@Test
public void testNext8(){
Date date = DateUtil.toDate("2008-09-11 19:19:19");
String cron = "0 0 0 1/2 MAR-AUG ?";
Date next = new CronSequenceGenerator(cron).next(date);
Assert.assertEquals("2009-03-01 00:00:00" , DateUtil.toStr(next));
Date next1 = CronUtil.next(cron, date);
Assert.assertEquals("2009-03-01 00:00:00" , DateUtil.toStr(next1));
}
@Test
public void testNext9(){
Date date = DateUtil.toDate("2003-02-09 06:17:19");
String cron = "0 10-20/3,57-59 * * * WED-FRI";
Date next = new CronSequenceGenerator(cron).next(date);
Assert.assertEquals("2003-02-12 00:10:00" , DateUtil.toStr(next));
Date next1 = CronUtil.next(cron, date);
Assert.assertEquals("2003-02-12 00:10:00" , DateUtil.toStr(next1));
}
@Test
public void testNext10(){
Date date = DateUtil.toDate("2016-12-28 19:01:35");
String cron = "0 10,44 14 ? 3 WED";
Date next = new CronSequenceGenerator(cron).next(date);
Assert.assertEquals("2017-03-01 14:10:00" , DateUtil.toStr(next));
Date next1 = CronUtil.next(cron, date);
Assert.assertEquals("2017-03-01 14:10:00" , DateUtil.toStr(next1));
}
@Test
public void testNext11(){
Date date = DateUtil.toDate("2018-11-18 12:00:12");
String cron = "0-12/12 00 12 ? * *";
Date next = new CronSequenceGenerator(cron).next(date);
Assert.assertEquals("2018-11-19 12:00:00" , DateUtil.toStr(next));
Date next1 = CronUtil.next(cron, date);
Assert.assertEquals("2018-11-19 12:00:00" , DateUtil.toStr(next1));
}
/**
* 以下是一些极端情况,属于撞大运的
*/
@Test
public void testNext12(){
Date date = DateUtil.toDate("2018-11-18 12:00:12");
String cron = "2 15 23 3 3 7";//0
Date next = new CronSequenceGenerator(cron).next(date);
Assert.assertEquals("2019-03-03 23:15:02" , DateUtil.toStr(next));
Date next1 = CronUtil.next(cron, date);
Assert.assertEquals("2019-03-03 23:15:02" , DateUtil.toStr(next1));
}
@Test(expected = IllegalArgumentException.class)
public void testNext13(){
Date date = DateUtil.toDate("2018-11-18 12:00:12");
String cron = "2 15 23 3 3 6";
///报错
/*Date next = new CronSequenceGenerator(cron).next(date);
Assert.assertEquals("2029-03-03 23:15:02" , DateUtil.toStr(next));*/
Date next1 = CronUtil.next(cron, date);
Assert.assertEquals("2029-03-03 23:15:02" , DateUtil.toStr(next1));
}
@Test
public void testNext14(){
Date date = DateUtil.toDate("2018-11-18 12:00:12");
String cron = "2 15 23 3 3 5";
///报错
/*Date next = new CronSequenceGenerator(cron).next(date);
Assert.assertEquals("2023-03-03 23:15:02" , DateUtil.toStr(next));*/
Date next1 = CronUtil.next(cron, date);
Assert.assertEquals("2023-03-03 23:15:02" , DateUtil.toStr(next1));
}
@Test
public void testNext15(){
Date date = DateUtil.toDate("2018-11-18 12:00:12");
String cron = "2 15 23 3 3 4";
Date next = new CronSequenceGenerator(cron).next(date);
Assert.assertEquals("2022-03-03 23:15:02" , DateUtil.toStr(next));
Date next1 = CronUtil.next(cron, date);
Assert.assertEquals("2022-03-03 23:15:02" , DateUtil.toStr(next1));
}
@Test
public void testNext16(){
Date date = DateUtil.toDate("2018-11-18 12:00:12");
String cron = "2 15 23 3 3 3";
Date next = new CronSequenceGenerator(cron).next(date);
Assert.assertEquals("2021-03-03 23:15:02" , DateUtil.toStr(next));
Date next1 = CronUtil.next(cron, date);
Assert.assertEquals("2021-03-03 23:15:02" , DateUtil.toStr(next1));
}
@Test
public void testNext17(){
Date date = DateUtil.toDate("2018-11-18 12:00:12");
String cron = "2 15 23 3 3 2";
Date next = new CronSequenceGenerator(cron).next(date);
Assert.assertEquals("2020-03-03 23:15:02" , DateUtil.toStr(next));
Date next1 = CronUtil.next(cron, date);
Assert.assertEquals("2020-03-03 23:15:02" , DateUtil.toStr(next1));
}
@Test(expected = IllegalArgumentException.class)
public void testNext18(){
Date date = DateUtil.toDate("2018-11-18 12:00:12");
String cron = "2 15 23 3 3 1";
///异常
Date next = new CronSequenceGenerator(cron).next(date);
Assert.assertEquals("2019-03-03 23:15:02" , DateUtil.toStr(next));
Date next1 = CronUtil.next(cron, date);
Assert.assertEquals("2025-03-03 23:15:02" , DateUtil.toStr(next1));
}
/**
* 一个比较复杂的表达式来测试 benchmark
* 1.由于Spring使用了BitSet数据结构,操作都是位运算,所以速度较快
* 2.HMS算法先计算所有的可能,再去找一个,比较耗时,并且基于一个假设,时分秒的组合不多,在遇到极端情况表现就非常糟糕
* 3.所以HMS算法仅仅作为学习之用
*/
@Ignore
@Test
public void benchmark(){
Date date = DateUtil.toDate("1999-10-18 12:00:00");
String cron = "10-20/4 10,44,30/2 10 ? 3 WED";
int max = 10;
long beginSpring = System.currentTimeMillis();
for (int i = 0; i < max; i++) {
new CronSequenceGenerator(cron).next(date);
}
System.out.println("Spring 执行 " + max + " 次耗时: " + (System.currentTimeMillis() - beginSpring));
long beginHms = System.currentTimeMillis();
for (int i = 0; i < max; i++) {
CronUtil.next(cron, date);
}
System.out.println("HMS 执行 " + max + " 次耗时: " + (System.currentTimeMillis() - beginHms));
}
}