首页 > 编程语言 >Java解析cron表达式

Java解析cron表达式

时间:2022-11-01 10:36:08浏览次数:62  
标签:return String List private cron result Java 表达式


概述

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表达式

如下图所示一个实际需求,需实现定时调度,其中周几、小时、分钟可配置化:

Java解析cron表达式_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.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() {
}
}

Spring类型和Quartz类型的区别,在最后一位符号:

Java解析cron表达式_java_02


而cron表达式的规则里面:第6位,即Day of week ,​​*​​号是包括​​?​​的。xxl-job平台使用的是QUARTZ类型:

Java解析cron表达式_cron表达式_03


证明:xxl-job使用的是quartz类型:

Java解析cron表达式_cron_04


证明:Spring类型是Quartz类型的超集,即兼容Quartz:

Java解析cron表达式_cron_05


Java解析cron表达式_cron_06


结论:

  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的这个功能:

Java解析cron表达式_spring_07


截图为公司内部基于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]);




标签:return,String,List,private,cron,result,Java,表达式
From: https://blog.51cto.com/u_15851118/5811966

相关文章

  • Java8学习笔记
    Java8引入函数式编程,好处:代码简洁,意图明确,使用stream接口而不是for循环。多核友好,parallel()方法。相关知识高阶函数高阶函数就是接收函数参数的函数,能够根据传入的函数参......
  • Java学习之位运算(操作)总结
    最近在反思工作第四年的深度,故而写此系列。其他Java系列文章:​​Java学习之编译、反编译以及字节码入门​​​​Java学习之String​​​​Java学习之JDK9新特性​​位操作,......
  • Java学习之NoClassDefFoundError、ClassNotFoundException、NoSuchMethodError
    在菜逼如我短短的三年职业编码生涯中,无数次遇到这两个异常,故而总结一下。Java异常体系大致提一些,不是本文的重点。两者都是标准异常,平时碰到最多的是ClassNotFoundExceptio......
  • Java学习之String
    概述写在前面,工作第四年,重新把基础抓起来吧。String可以说是JDK中最基础的一个类。就记录一些日常开发中最常用的方法。String类是非可变类,其对象一旦创建,就不可销毁。Strin......
  • Java学习之JDK9新特性
    写在前面:现在(2019-01-12)绝大多数的公司或者个人都在使用JDK8,这一点毋庸置疑,但是不排除那些需要自我反省一下的落后者还在使用JDK5~7。毕竟JDK12都出来了。参考​​​JDK12......
  • 面试之基础算法题:求一个数字在给定的已排序数组中出现的起始、终止索引号(Java版)
    题目给定一个升序的整数数组,查找某一个值在数组中出现的索引号,例如,输入数组​​[2,3,3,4,4,5]​​​;查找的数是3,则返回​​[1,2]​​。时间复杂度要求为O(logN)。思路......
  • java操作http请求的三种方式
    java操作http请求的三种方式一、HttpClient步骤:1.获取一个Http客户端CloseableHttpClienthttpClient=HttpClients.createDefault();2.创建一个请求HttpGethttpGet......
  • Java 基于 SpringCloud 数据中台 ETL 工具,可以进行多种常见数据库之间的数据或结构迁
    基于SpringCloud数据中台ETL工具,可以进行多种常见数据库之间的数据或结构迁移提供源端数据库向目的端数据库的批量迁移同步功能,支持数据的全量和增量方式同步。包括:......
  • Java多线程(7):JUC(上)
    您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来~ 前面把线程相关的生命周期、关键字、线程池(ThreadPool)、ThreadLocal、CAS、锁和AQS都讲完了,现在就剩下怎么来用多线程了......
  • JavaScript中Array.from()方法的用法
    1.介绍作用:将一个伪数组对象或者可迭代的任意对象转换为一个真正的数组语法:Array.from(arrayLike[,mapFunction[,thisArg]])arrayLike:必传参数,指定需要转换为数......