java_cron表达式_打印下次执行时间验证表达式是否正确

前言

cron表达式不常用,基本写好了很少去修改。不过在写的过程中会遇到一个问题,就是这个表达式对不对?我们怎么去验证,测试时不能等待这个任务去执行吧!
如果按分钟、按秒的调度还好说,等等可以。如果按日、月、周为周期的怎么等?
下面介绍如何通过CronTrigger类打印cron表达式的下次执行时间,验证cron表达式的方法。

环境

  • JDD 1.8

cron表达式格式

Cron 表达式是一个具有时间含义的字符串,分为 6 或 7 个域,每个域用空格分隔,一个域代表一种含义。
Cron 有如下两种语法格式:

  • 秒 分 时 日 月 周
  • 秒 分 时 日 月 周 年

cron各域字符含义及可选值

  • 取值范围
  • 秒 [0, 59]
  • 分 [0, 59]
  • 时 [0, 23]
  • 日 [1, 31]
  • 月 [1, 12]
  • 周 [1, 7] 其中 1 表示星期一,7 表示星期日。
  • 年 留空,1970~2099
  • 特殊字符
  • 秒 * , - /
  • 分 * , - /
  • 时 * , - /
  • 日 * , - / ? L W C
  • 月 * , - /
  • 周 * , - / ? L C #
  • 年 , - * /
  • 特殊字符含义
  • 字符:* 表示所有可能的值。如:在月份域中,* 表示每个月;在星期域中,* 表示星期的每一天。
  • 字符:, 表示枚举值。如:在分钟域中,5,20 表示分别在 5 分钟和 20 分钟触发一次。
  • 字符:- 表示范围 。如:在分钟域中,5-20 表示从 5 分钟到 20 分钟之间每隔一分钟触发一次。
  • 字符:/ 表示步长 。如:在分钟域中,0/15 表示从第 0 分钟开始,每 15 分钟触发一次。在分钟域中3/20表示从第 3 分钟开始,每 20 分钟触发一次。
  • 字符:? 表示不指定值,仅日期和星期域支持该字符 。如:当日期或星期域其中之一被指定了值以后,为了避免冲突,需要将另一个域的值设为?。
  • 字符:L 表示单词 Last 的首字母,即最后一天,仅日期和星期域支持该字符 。如:在日期域中,L 表示某个月的最后一天。在星期域中,L 表示一个星期的最后一天,也就是星期日(SUN)。如果在 L 前有具体的内容,例如,在星期域中的 6L,表示这个月的最后一个星期六。
  • 字符:W 表示有效工作日(周一到周五),只能出现在日期域,系统将在离指定日期最近的有效工作日触发事件。W 字符寻找当前月份中最近有效工作日,连用字符 LW 时表示为指定月份的最后一个工作日。
  • 字符:# 表示 。如:确定每个月第几个星期几,仅星期域支持该字符。
  • 字符:C 表示 。如:这个字符依靠一个指定的“日历”。也就是说这个表达式的值依赖于相关的“日历”的计算结果,如果没有“日历”关联,则等价于所有包含的“日历”。仅日期和星期域支持该字符。

Cron表达式示例

  • 0 15 10 ? * *
  • 每天上午 10:15 执行任务
  • 0 15 10 * * ?
  • 每天上午 10:15 执行任务
  • 0 0 12 * * ?
  • 每天中午 12:00 执行任务
  • 0 0 10,14,16 * * ?
  • 每天上午 10:00 点、下午 14:00 以及下午 16:00 执行任务
  • 0 0/30 9-17 * * ?
  • 每天上午 09:00 到下午 17:00 时间段内每隔半小时执行任务
  • 0 * 14 * * ?
  • 每天下午 14:00 到下午 14:59 时间段内每隔 1 分钟执行任务
  • 0 0-5 14 * * ?
  • 每天下午 14:00 到下午 14:05 时间段内每隔 1 分钟执行任务
  • 0 0/5 14 * * ?
  • 每天下午 14:00 到下午 14:55 时间段内每隔 5 分钟执行任务
  • 0 0/5 14,18 * * ?
  • 每天下午 14:00 到下午 14:55、下午 18:00 到下午 18:55 时间段内每隔 5 分钟执行任务
  • 0 0 12 ? * WED
  • 每个星期三中午 12:00 执行任务
  • 0 15 10 15 * ?
  • 每月 15 日上午 10:15 执行任务
  • 0 15 10 L * ?
  • 每月最后一日上午 10:15 执行任务
  • 0 15 10 ? * 6L
  • 每月最后一个星期六上午 10:15 执行任务
  • 0 15 10 ? * 6#3
  • 每月第三个星期六上午 10:15 执行任务
  • 0 10,44 14 ? 3 WED
  • 每年 3 月的每个星期三下午 14:10 和 14:44 执行任务

测试方法

import org.junit.Test;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.scheduling.support.SimpleTriggerContext;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;

public class CronTest {

    /*
    取值范围:
        秒   [0, 59]
        分   [0, 59]
        时   [0, 59]
        日   [1, 31]
        月   [1, 12]
        周   [1, 7]
    * */
    /** cron表达式验证,打印下次执行时间 */
    @Test
    public void nextCronTrigger(){
        String cron_s = "0/2 * * * * ? ";
        String cron_min = "0 0/2 * * * ?";
        String cron_h = "0 0 0/2 * * ?";
        String cron_d = "0 1 1 1/1 * ?";
        String cron_w = "0 1 1 * * 2";
        String cron_month = "0 1 1 1 1/1 ?";
        String cur = format(new Date());
        System.out.println("现\t"+cur);
        System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
        printNextExeTime("秒",cron_s);
        printNextExeTime("分",cron_min);
        printNextExeTime("时",cron_h);
        printNextExeTime("日",cron_d);
        printNextExeTime("周",cron_w);
        printNextExeTime("月",cron_month);
    }

    /** 打印下执行时间 */
    public void printNextExeTime(String msg,String cron){
        SimpleTriggerContext cxt = new SimpleTriggerContext();
        CronTrigger cronTrigger = new CronTrigger(cron);
        Date nextTime = cronTrigger.nextExecutionTime(cxt);
        String nextTimeStr = format(nextTime);
        System.out.println(msg+"\t"+nextTimeStr+"\t"+cron);
    }
    public String format(Date date){
        ZoneId zoneId = ZoneId.systemDefault();
        LocalDateTime localDate = date.toInstant().atZone(zoneId).toLocalDateTime();
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        return dateTimeFormatter.format(localDate);
    }
}

执行结果:

现	2023-11-14 14:24:01
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
秒	2023-11-14 14:24:02	0/2 * * * * ? 
分	2023-11-14 14:26:00	0 0/2 * * * ?
时	2023-11-14 16:00:00	0 0 0/2 * * ?
日	2023-11-15 01:01:00	0 1 1 1/1 * ?
周	2023-11-21 01:01:00	0 1 1 * * 2
月	2023-12-01 01:01:00	0 1 1 1 1/1 ?

Process finished with exit code 0

总结

通过上面的测试,可以方便的查看下次执行时间,去校验cron表达式是否正确。
需要注意的是 * 的使用,如写成这种形式 * */2 * * * ? 可能导致每秒钟都在执行。