Java解析cron表达式

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

调研

在线工具

很多,因为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.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类型的区别,在最后一位符号:
在这里插入图片描述
而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]);
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java解析cron表达式 的相关文章

  • 查找超过 1 小时的 -mtime 文件 [重复]

    这个问题在这里已经有答案了 我目前每 24 小时运行一次此命令 find var www html audio daystart maxdepth 1 mtime 1 type f name mp3 exec rm f 我想每 1 小时运行
  • 在 Windows 中设置 cron 作业

    我每天都必须从 SFTP 服务器下载文件 我有一个从服务器检索文件的程序 但我正在考虑设置一个 cron 作业 或任何类似的作业 来自动执行该操作 我们是一家 Windows 商店 需要在 Windows 中设置 cron 作业 windo
  • 使用 cron 表达式流口水规则?

    我有一个要求 我只想在工作日触发规则 我有一些规则 如烟雾 温度 运动 您能否建议我如何根据我的要求制定规则 请给我一些示例 除了 cron 之外 还有其他更好的方法来根据时间触发规则吗 您可以在工作日或周末解雇规则 我也遇到过同样的要求
  • 每周运行一次 php 脚本

    每个星期一我都需要清空 MYSQL 数据库的几个字段 首先 我考虑使用 cron 作业 但我的 Web 托管提供商 fatcow com 既不支持 SSH 也不支持通过命令行访问共享服务器 我还考虑过检查 date 以查看是否是星期一并执行
  • 为什么 cron 产生的进程最终会失效?

    我有一些进程显示为
  • 如何在 Laravel 5.6 中将 Cron 条目添加到 WAMP localhost

    我将在我的应用程序中的 laravel 5 6 中创建任务计划程序 我正在使用 Windows 7 操作系统 我的本地主机是 WAMP 在 laravel 文档中 将 Cron 条目添加到您的服务器中 cd path to your pro
  • 源 bashrc 在 cron 中不起作用

    我们都知道 cron 会忽略 bashrc 和 bash profile 中定义的变量 因此我们必须在 cron 中定义它 我经常做类似问题中写的同样的事情https unix stackexchange com questions 679
  • 将 crontab 文件替换为 -e

    有人知道如何将文件放入 crontab e 吗 我无法使用 crontab 的常规形式为其提供路径 但仍需要替换整个内容 也许将 EDITOR 设置为某些内容 您可以使用以下命令将 cron 作业从文件中获取到 crontab 中 cron
  • 具有多个服务器的计划任务 - 单点责任

    我们有一个 Spring JPA Web 应用程序 我们使用两个运行应用程序并使用相同数据库的 tomcat 服务器 您的应用程序要求之一是执行 cron 计划任务 经过简短的研究 我们发现 Spring 框架为 cron 作业提供了一个非
  • 如何在AWS EC2服务器中编写cron作业

    我在 AWS EC2 中创建了一个 cron 作业 但它不起作用 我按照以下步骤创建 crontab 第1步 我登录到AWS EC2实例 step 2 crontab e 第三步 插入模式 第4步 我输入了 php var www html
  • 气流:Dag 每隔几秒安排两次

    我尝试每天仅运行一次 DAG00 15 00 午夜 15 分钟 然而 它被安排了两次 间隔几秒钟 dag DAG my dag default args default args start date airflow utils dates
  • cronjob 上的 PHP 错误,在提示时工作正常

    我正在 cronjob 上运行以下脚本 cd etc parselog php run all php gt dev null 并收到以下错误 05 May 2009 20 30 12 PHP Warning PHP Startup Una
  • git commit 找不到在 cron 作业中运行的(全局)配置

    我想使用 cron 作业提交一些文件更改 调用一个脚本 并在 root crontab 中使用以下行 0 cd files backup sh gt tmp cronlog 2 gt tmp cronerror 该脚本如下所示 usr bi
  • App Engine Cron 作业始终返回 HTTP 状态代码 301

    我已关注本指南 https cloud google com appengine docs flexible ruby scheduling jobs with cron yaml为我的 Rails 应用程序创建 cron 作业 但 HTT
  • 在 PHP 中使用消息队列与普通 Cron 作业之间的区别

    我们有一个基于 PHP 构建的大型 Web 应用程序 该应用程序允许安排推文和墙贴 并且有从服务器发出的预定电子邮件 我所说的 计划 是指这些 PHP 脚本计划在特定时间运行cron 大约有 7 个 PHP 文件可以完成上述工作 我一直听说
  • Quartz 调度程序执行 Runnable

    Quartz Scheduler 可以执行 Runnable 吗 例如 我有以下代码正在由 spring 运行TaskScheduler Autowired Qualifier IntegrationConfiguration TASK S
  • Spring cron 表达式每 30 分钟一次

    Java spring 我有以下 cron 作业的 cron 表达式 0 0 35 但上面提到的 cron 表达式每小时触发一次 如下所示 1 35 2 35 3 35 4 35 我想每 35 分钟触发一次 而不是一小时触发一次 有什么快速
  • Cron 作业中的 PyAutoGUI

    我正在尝试运行一个程序 该程序可以通过 crontab 使用 Selenium 和 PyAutoGUI 在 python 3 6 中自动拉出一些选项卡 这是当 cron 不运行该程序时我尝试运行的脚本 import pyautogui im
  • 使用 Webmin 设置 cron 作业

    我正在尝试使用 Webmin 设置一个 Cron 作业每 5 分钟运行一次 它需要定位 php 文件并运行该文件中的 php 脚本 当我在 命令 字段中输入文件路径时 它不起作用 我只是想知道我做错了什么 以及我需要 Cron 作业运行的文
  • 每 30 秒运行一次 Laravel 方法

    我有一个在我调用应用程序上的特定 URL 时运行的方法 它处理数据库队列中的行 该间隔被设置为 Cron 可能的最小间隔 即 1分钟 这需要减少到 30 秒 所以我想知道如何最好地实现这一目标 我想我可以在我的脚本中构建一个循环 运行代码两

随机推荐

  • 机器学习之人脸识别(Face Recognition)

    机器学习之机器是如何识别人脸 Face Recognition 的 目前 一些机器学习技术已经被广泛应用于人脸识别 人脸支付以及身份认证领域 例如支付宝的FACEID 阿里的Alipay ETC等等 这个领域内的算法多以传统的Eigen F
  • [转]虚拟驾舱Cockpit可选的芯片平台

    如果你认为本系列文章对你有所帮助 请大家有钱的捧个钱场 点击此处赞助 赞助额0 1元起步 多少随意 声明 本文只用于个人学习交流 若不慎造成侵权 请及时联系我 立即予以改正 锋影 email 174176320 qq com 与传统的多芯片
  • redis的多路复用原理

    redis服务端对于命令的处理是单线程的 但是在I O层面却可以同时面对多个客户端并发的提供服务 并发到内部单线程的转化通过多路复用框架实现 一个IO操作的完整流程是数据请求先从用户态到内核态 也就是操作系统层面 然后再调用操作系统提供的a
  • mysql表示数字的数据类型的长度

    在mysql当中表示数字的数据类型 有这么几个 从小到大以此是 tinyint 128 127 smallint 32 768 32767 mediumint 8 388 608 8388607 这三个对应java的数据类型是int类型 i
  • Java-Java绘图坐标体系

    坐标体系介绍 坐标原点位于左上角 以像素为单位 在java坐标系中 第一个是x坐标 表示当前位置为水平方向 距离坐标原点x个像素 第二个是y坐标 表示当前位置为垂直防线 距离坐标原点y个像素 像素介绍 像素是一个密度单位 计算机在屏幕上显示
  • httpservletresponse 获取body_获取请求体数据

    将一些获取请求体数据 请求体数据post请求的时候才有
  • 关于github在vscode上的认证以及密钥缓存机制

    今天在向GitHub仓库提交代码的时候收到了这封邮件 说是使用密码的认证将要被舍弃了 提醒我换成两步验证 2FA 切换成两步验证很顺利 突然很好奇GitHub密码在Mac上是怎么保存的 vscode的设置里有两个选项 如下图 保存密码的地方
  • html如何添加环绕标签,html给定标签选项并添加标签(附代码)

    这篇文章给大家介绍的内容是关于html给定标签选项并添加标签 附代码 有一定的参考价值 有需要的朋友可以参考一下 希望对你有所帮助 HTML haveTags addTags 返回的数组 CSS havetags span addtags
  • ctfshow 萌新web系列--3

  • Linux shell判断含有通配符的文件是否存在

    方法一 使用 ls jpg gt dev null 命令 if ls jpg gt dev null then echo 当前文件夹下 未找到 jpg文件 else echo 当前文件夹下 存在 jpg文件 fi 方法二 使用 ls jpg
  • Descriptors cannot not be created directly

    1 Descriptors cannot not be created directly 在运行诸如深度学习python等程序时 如mmdetection mmdetection3d中的程序 会出现报错 Descriptors cannot
  • 后氧传感器正常数据_氧传感器电压多少正常?氧传感器数据流分析介绍

    氧传感器作用是什么 氧传感器用以检测排气中氧的浓度 并向ECU发出反馈信号 再由ECU控制喷油器喷油量的增减 从而将混合气的空燃比控制在理论值附近 氧传感器是利用陶瓷敏感元件测量汽车排气管道中的氧电势 由化学平衡原理计算出对应的氧浓度 达到
  • Redis启动与关闭

    安装redis之后 在命令行窗口中输入 redis server redis windows conf 启动redis 关闭命令行窗口就是关闭 redis redis作为windows服务启动方式 redis server service
  • Xilinx_RAM_IP核的使用

    Xilinx RAM IP核的使用 说明 单口RAM 伪双口RAM 双口RAM的读写 以及RAM资源占用的分析 环境 Vivado2018 3 IP核 Block Memory Generator 参考手册 UG473 7 Series F
  • 人力资源平台项目总结(2)

    目录 1 路由和页面 1 1 左侧菜单的显示逻辑 设置菜单图标 重点 2 组织架构 2 1 认识组织架构 2 2 将树形的操作内容单独抽提成组件 2 3 获取组织架构数据 并进行树形处理 重点 2 4 删除部门功能实现 2 5 新增部门功能
  • 使用presto+airpal+hive打造即席查询工具

    0X01 前言 即席查询怎么做 怎么选型 这次用的是presto来做尝试 缘起 公司是Impala的深度用户 我主要负责Impala的各方面的工作 最近因为一些特殊原因需要对现有的体系进行一些调整 需要做出来即席查询的组件 在spark s
  • 基于matlab的多元线性回归分析

    二 多元线性回归原理 2 1 数学模型 在社会生活及生产实践中会经常遇到一种问题 即我们非常关注一个量的变化 而这个量受到另一个或是多个因素的影响 我们想要了解这些因素是如何影响我们最为关注的这个量的以及这些因素对我们最为关注的这个量的影响
  • 【C语言进阶】实现atoi函数

    1 函数介绍 atoi的函数功能 将string所指向数字字符串转化为整数 注意 1 会跳过前面的空白字符 例如空格 tab缩进 等 2 如果不能转换成 int 或者为空字符串 那么将返回 0 特别注意 该函数要求被转换的字符串是按十进制数
  • 数字图像处理-小波变换小白解释基本原则

    内容完全转载 小波理论的基本概念及概述 第二版 欢迎阅读此份关于小波变换的入门教程 小波变换是一个相对较新的概念 其出现大约是在20世纪80年代 但是有关于它的文章和书籍却不少 这其中大部分都是由数学专业人士写给其他同行看的 不过 仍然有大
  • Java解析cron表达式

    概述 Cron表达式是一个字符串 以5或6个空格隔开 分为6或7个域 每一个域代表一个含义 即两种语法格式 Seconds Minutes Hours DayofMonth Month DayofWeek Year 即 秒 分 时 天 月