Java解析cron表达式

article/2025/9/18 23:17:36

概述

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位互斥)有冲突,必须指定一个,两者不能同时指定;*指任意一天算指定,?不算指定;不能两者都是*;结论:这两个符号有且只能有一个必是问号?
    在这里插入图片描述
    在这里插入图片描述

调研

在线工具

很多,因为cron表达式有各种不同的类型,不同类型直接还是有一些细微的差别。
https://www.bejson.com/othertools/cron/

spring scheduling

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

quartz

org.quartz.CronExpression

cron-utils

官网:http://cron-parser.com/
GitHub
https://awesomeopensource.com/project/jmrozanec/cron-utils
https://www.openhub.net/p/cron-utils

maven

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

cron-parser

GitHub
https://suhasjavablog.wordpress.com/2014/04/01/how-to-generate-a-cron-expression-from-a-date-object/

实践

校验cron表达式合法性

参考下面checkValid方法。

构建cron表达式

如下图所示一个实际需求,需实现定时调度,其中周几、小时、分钟可配置化:
在这里插入图片描述
对应到cron表达式里面,也就是第2、3、6位字符需要支持可配置化。

基于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.QUARTZCronDefinition 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);}@Datapublic 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() {}
}

Spring类型和Quartz类型的区别,在最后一位符号:
在这里插入图片描述
而cron表达式的规则里面:第6位,即Day of week ,*号是包括?的。

xxl-job平台使用的是QUARTZ类型:
在这里插入图片描述
证明:xxl-job使用的是quartz类型:
在这里插入图片描述
证明:Spring类型是Quartz类型的超集,即兼容Quartz:
在这里插入图片描述
在这里插入图片描述
结论:

  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次执行时间

实现效果预览,类似于xxl-job的这个功能:
在这里插入图片描述
截图为公司内部基于xxl-job的二次开发任务调度平台;在xxl-job GitHub源码里面搜了下,没有看到具体的实现代码逻辑。

于是自己基于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]);

http://chatgpt.dhexx.cn/article/W93vReTg.shtml

相关文章

Cron表达式详细用法

目录 Cron表达式一、秒二、分钟三、小时四、日期五、月份六、星期七、年份八、特殊字符九、表达式举例 Cron表达式 Cron表达式的长度为6或7位&#xff0c;其中第7位年份可省略&#xff0c;省略时表示每年。 Scheduled(cron"* * * * * * *") 这7位从左到右分别对应&…

一看就懂:cron 表达式

【前言】 不知道你在玩游戏的时候是否发现过以下情况&#xff1a; &#xff08;1&#xff09;玩某些游戏的时候&#xff0c;发现他的排行榜并不是时时更新的&#xff0c;而是每半个小时&#xff0c;或者一个小时更新一次。 &#xff08;2&#xff09;又比如很火的王者荣耀手…

cron表达式的详细介绍(各域说明以及举例说明)

cron表达式的详细介绍&#xff08;各域说明以及举例说明&#xff09; 1. 关于 cron1.1 前言1.2 使用 cron 的注意点1.3 举个例子 2. cron表达式的格式&#xff08;或语法&#xff09;3. 各域的含义——如何取值&#xff1f;3.1 各域的含义介绍3.2 特殊字符的含义3.3 简单举例说…

使用cron定时执行任务

本文主要介绍Unix系统中用于定时执行任务的cron守护程序和crontab配置表的文件格式&#xff0c;并对自动执行脚本文件时的注意事项进行了说明&#xff0c;主要包括以下几个方面&#xff1a; cron守护程序概述crontab配置表crontab配置举例执行命令脚本 cron是一个用于执行计划…

cron计划任务

这里写目录标题 总结一、cron(crond)简介二、crontab命令1&#xff0e;命令格式&#xff1a;2&#xff0e;命令功能&#xff1a;3&#xff0e;命令参数&#xff1a;第一种形式&#xff08;将原先存在的文件加入到corn中&#xff09;第二种形式&#xff08;用命令行的形式加入到…

Cron表达式

Cron表达式语法 一、结构 corn从左到右&#xff08;用空格隔开&#xff09;&#xff1a;秒 分 小时 日 月 星期 年 二、各字段的含义 &#xff08;1&#xff09;*&#xff1a;表示匹配该域的任意值。假如在Minutes域使用*, 即表示每分钟都会触发事件。 &#xff08;2&#x…

C语言结构体内存对齐

结构体内存对齐 如何计算结构体的大小&#xff1f; 首先得掌握结构体的对齐规则: 1.第一个成员在与结构体变量偏移量为0的地址处。(将第一个成员放在结构体内存的第0处) 2.其他成员变量要对齐到某个数字&#xff08;对齐数&#xff09;的整数倍的地址处。&#xff08;从0地…

C语言结构体对齐详解

文章目录 一、C语言结构体对齐大小快速判断二、反汇编角度看结构体三、总结 一、C语言结构体对齐大小快速判断 在C语言中定义一个结构体&#xff0c;里面具体占用多少个字节呢&#xff0c;先举一个例子&#xff0c;如下&#xff1a; #include<stdio.h> #pragma pack(8)…

C语言结构体传参

目录 C语言结构体传参1. 普通传参1.1 测试代码1.2 测试结果1.3 结果分析 2. 单指针传参2.1 修改结构体数据2.1.1 测试代码2.1.2 测试结果2.1.3 结果分析 2.2 修改结构体地址2.2.1 测试代码2.2.2 测试结果2.2.3 结果分析 3. 双指针传参3.1 测试代码3.2 测试结果3.2 结果分析 C语…

C语言 结构体

1什么是结构体 结构体是一种集合&#xff0c;它里面包含了多个变量或数组&#xff0c;它们的类型可以相同&#xff0c;也可以不同&#xff0c;每个这样的变量或数组都称为结构体的成员。结构的成员可以是标量、数组、指针&#xff0c;甚至是其他结构体。 2结构体的定义 (1&a…

c语言:结构体(详解)

初识结构体 一.结构体声明1.结构体的概念2.声明 二.结构体的基础使用三.结构体变量的定义和初始化四.空结构体五.柔性数组1.定义2.使用 六.结构体内存对齐七.位端 一.结构体声明 1.结构体的概念 结构体是一些值的集合&#xff0c;这些值称为成员变量。结构的每个成员可以是不同…

C语言结构体详解

目录 一、结构体的基本概念 举个例子 二、结构体变量 三、结构体占用的内存情况 举个例子 运行效果 再次运行 四、结构体的变量名 五、结构体初始化 五、结构体初始化 举个例子 运行效果 六、结构体成员的访问 举个例子 运行效果 八、结构体指针 举个例子 运行效…

C语言结构体超详解(小白一看就懂,多维度分析!!!!)

目录 一、前言 二、结构体详解 &#x1f350;什么是结构体 &#x1f34e;结构体的定义与基础结构 &#x1f351;结构体的使用 &#x1f4a6;结构体的初始化 &#x1f4a6;结构体的成员访问 &#x1f4a6;结构体数组 &#x1f4a6;结构体指针--------------指向结构体变…

C语言之结构体(进阶篇)

目录 1.结构体的内存对齐​ 如何计算呢&#xff1f;​ 掌握结构体的对齐规则&#xff1a; 为什么存在内存对齐呢&#xff1f;​ ​​​​​​​offsetof​ 位段​ 什么是位段&#xff1f;​ 比如&#xff1a; 位段的内存分配​ 举个例子 位段的跨平台问题&#xff1a;​ 枚举…

C语言——结构体(全)

目录 一、结构体的设计 二、结构体变量的初始化 2.1结构体在内存表示&#xff1b; 2.2结构体初始化&#xff1b; 2.3结构体指针变量 2.4结构体嵌套结构体 三、结构体成员访问 3.1、结构体成员访问 3.2、结构体变量和指针 ​3.3、结构体和函数 四、结构体与数组 五、…

Github客户端下载慢的解决方法

Github客户端下载慢的解决方法 Github客户端下载解决方法获取下载连接 Github客户端下载 Github客户端的下载地址是——Github客户端下载。 但是下载速度特别慢&#xff0c;最快也就十几K。 解决方法 使用迅雷下载&#xff1a;将Github客户端的下载地址复制到迅雷中&#…

windows平台下使用Github(2 创建代码仓库,安装Github客户端.)

本文会分章节的来介绍如何在windows平台下使用GitHub 一、注册Github账号 查看 二、创建代码仓库&#xff0c;安装Github客户端.查看 三、Github上传和下载(1 客户端方式)查看 四、Github上传和下载(2 命令行方式)查看 1、创建一个代码托管仓库。 点击右上角的 号或者点…

github客户端进行token认证

从 2021 年 8 月 13 日起&#xff0c;GitHub 在对 Git 操作进行身份验证时不再接受帐户密码。您需要添加 PAT&#xff08;个人访问令牌&#xff09;&#xff0c;您可以按照以下方法在您的系统上添加 PAT。 git - Support for password authentication was removed. Please us…

【解决】GitHub 客户端下载后安装在哪里?

GitHub Desktop 配置环境变量GitHub 安装路径GitHub Shell、Bash 环境是 Windows 10。 从 GitHub 官网下载了 GitHub 的 Desktop 版本之后&#xff0c;它其实不仅是一个 GUI&#xff0c;还带了 Shell&#xff08;Bash&#xff09;&#xff0c;但是发现找不到它们是被安装在哪个…

GitHub客户端上传本地代码

创建GitHub账号、安装GitHub客户端登录GitHub网站、在客户端登录账户创建新的代码存放处 填写及勾选一些信息 复制此代码存放处的路径 利用客户端上传代码 第一步&#xff1a;点击第五步的Open in Desktop&#xff0c;打开本地客户端 第二步&#xff1a;点击下图蓝色选中选…