cron(二)根据cron表达式计算给定时间的下一个执行时间

参见 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<CronField> 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<Integer> 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<Integer> listHour = fieldHour.points();
        List<Integer> listMinute = fieldMinute.points();
        List<Integer> listSecond = fieldSecond.points();


        //找到所有时分秒的组合
        List<TimeOfDay> 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 yanshixiong@126.com 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));
    }
}

 

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值