Java解析cron表达式

Java解析cron表达式7 段式 6 段式 5 段式 各个占位支持的特殊字符 cron 表达式一次性理解透彻 Java 中 cron 解析工具 自研 cronUtil 工具类 校验 cron 表达式合法性 构建 cron 表达式 预测 cron 表达式最近 10 次执行时间

概述

Cron表达式是一个字符串,以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,即两种语法格式:

  1. Seconds Minutes Hours DayofMonth Month DayofWeek Year,即:秒 分 时 天 月 星期 年份
  2. Seconds Minutes Hours DayofMonth Month DayofWeek

一般情况下,第七个字符Year可省略不写。

除此以外,也有五段表达式的,如crontab,没有秒的概念。绝大多数情况下,都是6个字符,本文讨论的也是6个字符。

知识点:

  1. 每个字符都允许设置, - * /四个特殊字符;
  2. 每个元素可以是一个值(如6),连续区间(9-12),间隔时间(8-18/4)(/表示每隔4个单位),列表(1,3,5),*通配符;
  3. 日期,即第4位还支持? L W C四个特殊字符;
  4. 星期,即第6位还支持? L C #四个特殊字符,可用3位大写英文字母表示(不常用),即1==SUN,另外1表示周日;
  5. L:last,表示最后,只能出现在第4和6个字符位。如果在DayofWeek域使用5L,意味着在最后的一个星期四触发;
  6. W:表示有效工作日(周一到周五),只能出现在第4位,系统将在离指定日期的最近的有效工作日触发事件。例如:在第4位使用5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日(周一)触发;如果5日在星期一到星期五中的一天,则就在5日触发。W的最近寻找不会跨过月份;
  7. LW:两个字符连用,表示在某个月最后一个工作日,即最后一个星期五;
  8. #:用于确定每个月第几个星期几,只能出现在第6位。如4#2表示某月的第二个星期三;
  9. 星期和日字段(第4和6位互斥)有冲突,必须指定一个,两者不能同时指定;*指任意一天算指定,?不算指定;不能两者都是*;结论:这两个符号有且只能有一个必是问号?
    在这里插入图片描述
    在这里插入图片描述




调研

在线工具

spring scheduling

在spring-context artifact的springframework.scheduling包下面,CronSequenceGenerator

quartz

org.quartz.CronExpression

cron-utils

maven

<dependency> <groupId>com.cronutils 
     groupId> <artifactId>cron-utils 
      artifactId> <version>9.1.5 
       version>  
        dependency> 

cron-parser

实践

校验cron表达式合法性

参考下面checkValid方法。

构建cron表达式

基于cron-utils写的一个工具类;

import com.cronutils.model.CronType; import com.cronutils.model.definition.CronDefinition; import com.cronutils.model.definition.CronDefinitionBuilder; import com.cronutils.parser.CronParser; import com.google.common.collect.Lists; import lombok.Data; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @Component public class CronUtil { 
    private static final Logger LOGGER = LoggerFactory.getLogger(CronUtil.class); private static final String QUESTION = "?"; private static final String ASTERISK = "*"; private static final String COMMA = ","; / * 替换 分钟、小时、日期、星期 */ private static final String ORIGINAL_CRON = "0 %s %s %s * %s"; / * 检查cron表达式的合法性 * * @param cron cron exp * @return true if valid */ public boolean checkValid(String cron) { 
    try { 
    // SPRING应该是使用最广泛的类型,但假若任务调度依赖于xxl-job平台,则需要调整为CronType.QUARTZ CronDefinition cronDefinition = CronDefinitionBuilder.instanceDefinitionFor(CronType.SPRING); CronParser parser = new CronParser(cronDefinition); parser.parse(cron); } catch (IllegalArgumentException e) { 
    LOGGER.error(String.format("cron=%s not valid", cron)); return false; } return true; } public String buildCron(List<Integer> minutes, List<Integer> hours, List<Integer> weekdays) { 
    String minute; if (minutes.equals(this.getInitMinutes())) { 
    minute = ASTERISK; } else { 
    minute = StringUtils.join(minutes, COMMA); } String hour; if (hours.equals(this.getInitHours())) { 
    hour = ASTERISK; } else { 
    hour = StringUtils.join(hours, COMMA); } String weekday; if (weekdays.equals(this.getInitWeekdays())) { 
    weekday = QUESTION; } else { 
    weekday = StringUtils.join(weekdays, COMMA); } // 重点:星期和日字段冲突,判断周日的前端输入 if (weekday.equals(QUESTION)) { 
    return String.format(ORIGINAL_CRON, minute, hour, ASTERISK, weekday); } else { 
    return String.format(ORIGINAL_CRON, minute, hour, QUESTION, weekday); } } / * 解析db cron expression展示到前端 * * @param cron cron * @return minutes/hours/weekdays */ public CustomCronField parseCon(String cron) { 
    if (!this.checkValid(cron)) { 
    return null; } List<String> result = Arrays.asList(cron.trim().split(" ")); CustomCronField field = new CustomCronField(); if (result.get(1).contains(COMMA)) { 
    field.setMinutes(Arrays.stream(result.get(1).split(COMMA)).map(Integer::parseInt).collect(Collectors.toList())); } else if (result.get(1).equals(ASTERISK)) { 
    field.setMinutes(this.getInitMinutes()); } else { 
    field.setMinutes(Lists.newArrayList(Integer.parseInt(result.get(1)))); } if (result.get(2).contains(COMMA)) { 
    field.setHours(Arrays.stream(result.get(2).split(COMMA)).map(Integer::parseInt).collect(Collectors.toList())); } else if (result.get(2).equals(ASTERISK)) { 
    field.setHours(this.getInitHours()); } else { 
    field.setHours(Lists.newArrayList(Integer.parseInt(result.get(2)))); } if (result.get(5).contains(COMMA)) { 
    field.setWeekdays(Arrays.stream(result.get(5).split(COMMA)).map(Integer::parseInt).collect(Collectors.toList())); } else if (result.get(5).equals(QUESTION)) { 
    field.setWeekdays(this.getInitWeekdays()); } else { 
    field.setWeekdays(Lists.newArrayList(Integer.parseInt(result.get(5)))); } return field; } private List<Integer> initArray(Integer num) { 
    List<Integer> result = Lists.newArrayListWithCapacity(num); for (int i = 0; i <= num; i++) { 
    result.add(i); } return result; } private List<Integer> getInitMinutes() { 
    return this.initArray(59); } private List<Integer> getInitHours() { 
    return this.initArray(23); } private List<Integer> getInitWeekdays() { 
    return this.initArray(7).subList(1, 8); } @Data public static class CustomCronField { 
    private List<Integer> minutes; private List<Integer> hours; private List<Integer> weekdays; } } 

表达式类型

cron-utils给出的cron表达式类型枚举类

public enum CronType { 
    CRON4J, QUARTZ, UNIX, SPRING; private CronType() { 
    } } 
  1. 如果开发的功能依赖于xxl-job调度任务,需要明确使用的xxl-job的版本,及使用的cron表达式类型,然后在代码里面写相同的类型;
  2. 对于其他任何调度系统,一定要先明确其支持的cron表达式类型,否则会出现任务没有执行的情况

Java(Spring)与Java(Quartz)

根据crontab,Java语言有两种,区别:

  1. Quartz支持7位,第7位可选;
  2. 第6位,只支持1-7;而Spring支持0-7,0和7都表示sun;

预测cron表达式最近10次执行时间

于是自己基于cron-utils实现如下:

public static List<String> getExecutionTimeByNum(String cronStr, Integer num) { 
    CronParser parser = new CronParser(CronDefinitionBuilder.instanceDefinitionFor(CronType.SPRING)); Cron cron = parser.parse(cronStr); ExecutionTime time = ExecutionTime.forCron(cron); ZonedDateTime now = ZonedDateTime.now(); ZonedDateTime next = getNext(time, now); List<ZonedDateTime> timeList = new ArrayList<>(num); timeList.add(next); for (int i = 1; i < num; i++) { 
    next = getNext(time, next); timeList.add(next); } DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); List<String> resultList = new ArrayList<>(num); for (ZonedDateTime item : timeList) { 
    String result = item.format(format); resultList.add(result); } return resultList; } private static ZonedDateTime getNext(ExecutionTime time, ZonedDateTime current) { 
    return time.nextExecution(current).get(); } 

在调用方法`getExecutionTimeByNum“前,可以先校验一下合法性。

判断cron是否是按天执行

/ * 判断cron是否是按天执行 * 如果按天执行cron需以(* * ?)结尾 * @return true 是以* * ?结尾 */ public static Boolean datasetCron(String cron) { 
    return StringUtils.isNotBlank(cron) && cron.matches(".* \\* \\* \\?$"); } // 判断是否按天更新 boolean day = "*".equals(dataset.getCronExp().split(" ")[3]); 
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/221597.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月17日 下午5:43
下一篇 2026年3月17日 下午5:44


相关推荐

  • Vue子组件向父组件传值(this.$emit()方法)「建议收藏」

    Vue子组件向父组件传值(this.$emit()方法)「建议收藏」子组件使用this.$emit()向父组件传值首先必须在父组件中引用子组件,然后实现传值 第一步在父组件中引入子组件 使用import引入组件importindexImportOrderfrom’./components/indexImportOrder’声明//定义组件components:{indexImportOrder,…

    2022年10月2日
    3
  • tensorflow2.0卷积神经网络_python神经网络框架

    tensorflow2.0卷积神经网络_python神经网络框架卷积神经网络一般用来处理图像信息,对于序列这种一维的数据而言,我们就得采用一维的卷积,tensorflow中提供有专用的函数conv1d,各参数的使用说明如下:conv1d参数说明value输入数据,value的格式为:[batch,in_width,in_channels],batch为样本维,表示多少个样本,in_width为宽度维,表示样本的宽度,in_channels维通道维,表示样本有多少个通道。filters卷积核,filters的格式为:[filter_wi

    2026年2月17日
    6
  • 红黑树与平衡二叉树区别?

    红黑树与平衡二叉树区别?如果说平衡二叉树是一个类的话 那么红黑树就是该类的一个实例 算法的书我丢久了 一下子也找不到 我是凭记忆说的 红黑树的算法比较麻烦 但它的思想很好 如果理解了它的思想也就理解它的算法 我也只记得思想 具体算法记不得了 我就在这说说思想吧 红黑树有两个重要性质 1 红节点的孩子节点不能是红节点 2 从根到前端节点的任意一条路径上的黑节点数目一样多 这两条性质确保该树的高度为 l

    2026年3月26日
    2
  • 背包九讲详解

    背包九讲详解背包九讲详解0-1背包问题完全背包问题多重背包问题

    2022年6月28日
    26
  • J2EE的13个规范之(二) JDBC 及其使用「建议收藏」

    J2EE的13个规范之(二) JDBC 及其使用

    2022年1月26日
    38
  • webstorm激活码【2021最新】

    (webstorm激活码)好多小伙伴总是说激活码老是失效,太麻烦,关注/收藏全栈君太难教程,2021永久激活的方法等着你。IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.net/100143.htmlWKAWTQAJR5-eyJsaWNlbnNlSWQi…

    2022年3月22日
    51

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号