写在前面

cron在希腊语中是时间的意思,而cron表达式(cron expression)则是遵循特定规则,用于描述定时设置的字符串,常用于执行定时任务。本文总结了不同环境(如平台、库等)下,cron expression在用数字表示星期上的区别,并进行实际验证。目的是消除疑惑,还原真相,以免读者在开发中“踩坑”。本文不详述cron含义及cron expression规范,有兴趣的读者请参考[1],[2],[3]和[4]。想直奔结论的读者请直接跳至结论。同时,建议您阅读建议小节。

正文

现象

在下刚接触cron expression,发现对于cron expression中如何用数字表示星期,网上流传着两个不同版本。版本1:1-6分别表示星期一-星期六,而0和7都可以表示星期日,如[2];版本2:1表示星期日,2表示星期一,……,7表示星期六,如[3]和[4]。

结论

到底哪个对呢?经验证,都对。于是,得出结论如下:

cron expression是“人格分裂者”:

  • 正常人格(版本1):1-6分别表示星期一-星期六,而0和7都可以表示星期日。存在于:类Unix系统中的定时任务管理服务cron,Golang的定时任务库cron,Spring-Task等。
  • 别扭人格(版本2):1-7分别表示星期日-星期六,即除1表示星期日外,其余都是数字n表示星期n-1。存在于:Quartz、Oracle Role Manager等。

验证

【必读】

  1. 本文对Ubuntu下cron服务,Golang的cron库,Spring-Task,以及Quartz进行了验证,其中,Ubuntu版本为20.04 LTS,cron库版本为3.0.1,Spring版本为5.2.7,Quartz版本为2.3.0。
  2. 验证2、3、4所需材料(包括源码、库、配置文件、编译运行脚本等,以下简称“验证材料”)已上传到这里(提取码: 4pkz),有兴趣的读者下载即可一键运行,只要您已安装并正确配置Java和Golang,并保证网络畅通(Golang的cron库需要在线下载)。您无需准备Spring库和Quartz库,它们已包含于在下为您准备的验证材料中。您还需要根据您执行代码的时间修改代码中的cron expression。
  3. 本文不会讨论各代码段的技术细节,给出代码仅仅是为了验证本文结论。
  4. Oracle Role Manager不知如何验证,暂不验证。这里恳请知道的读者不吝赐教。

验证1 Ubuntu下cron服务

注意,截图中涉及私人信息的部分已码掉。验证1的操作可参考这里这里这里

step1 准备工作如下图:


View Pic

step2 如上图最后一步所示,使用 crontab -e 命令打开配置文件编辑窗口,添加 * * * * 4 date >> /tmp/test_cron.txt 一行,意思是每逢星期四,就以每隔1分钟的频率将 date 命令执行结果以追加方式写入 /tmp/test_cron.txt 。如下图:


View Pic

step3 结果如下:


View Pic

验证2 Golang的cron库

代码如下:

 1 package main
2
3 import (
4 "fmt"
5 "github.com/robfig/cron/v3"
6 "time"
7 )
8
9 func main() {
10 // 开启秒字段支持
11 c := cron.New(cron.WithSeconds())
12 // 含义是: 每逢周二,就每秒执行一次
13 _, _ = c.AddFunc("0/1 * * * * 2", func() {
14 fmt.Println(time.Now().Format(time.ANSIC))
15 })
16 // 启动
17 c.Start()
18 // 防止程序直接退出
19 time.Sleep(time.Second * 3)
20 }

test.go

结果如下:

View Pic

验证3 Spring-Task

Spring实现定时任务有两种方式,xml方式和注解方式,下面给出注解方式代码,xml实现包含于验证材料中。共分为三个文件:

 1 import org.springframework.scheduling.annotation.Scheduled;
2 import java.util.Date;
3
4 public class MyTask //定义要执行的任务(函数)
5 {
6 //含有cron expression的注解,因为是周一进行的实验,所以条件改为“每逢周一,就每秒执行一次”
7 @Scheduled(cron = "0/1 * * ? * 1")
8 public void show()
9 {
10 System.out.printf("%tc%n",new Date());
11 }
12 }

MyTask.java

注意,上面的cron expression中,'?'不能改为'*'。'?'意味着“忽略,不考虑该维度”;'*'意味着“任意,所有”。如果将'?'改为'*',Spring会认为发生了“每天”和“每周一”的冲突。

下面的代码大体上相当于配置文件,没仔细研究过。

 1 import java.util.concurrent.Executor;
2 import java.util.concurrent.Executors;
3 import org.springframework.context.annotation.Bean;
4 import org.springframework.context.annotation.Configuration;
5 import org.springframework.scheduling.annotation.EnableScheduling;
6 import org.springframework.scheduling.annotation.SchedulingConfigurer;
7 import org.springframework.scheduling.config.ScheduledTaskRegistrar;
8
9 @Configuration
10 @EnableScheduling
11 public class Config implements SchedulingConfigurer
12 {
13
14 @Bean
15 public MyTask bean()
16 {
17 return new MyTask();
18 }
19
20 @Override
21 public void configureTasks(ScheduledTaskRegistrar taskRegistrar)
22 {
23 taskRegistrar.setScheduler(taskExecutor());
24 }
25
26 @Bean(destroyMethod="shutdown")
27 public Executor taskExecutor()
28 {
29 return Executors.newScheduledThreadPool(2);
30 }
31 }

Config.java

最后是测试文件:

 1 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
2 import org.springframework.context.support.AbstractApplicationContext;
3
4 public class Test
5 {
6 public static void main(String[] args)
7 {
8 AbstractApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
9 try
10 {
11 Thread.sleep(3000);
12 }
13 catch (InterruptedException e)
14 {
15 e.printStackTrace();
16 }
17 System.exit(0);
18 }
19 }

Test.java

结果如下:

View Pic

验证4 Quartz

代码如下,分两个文件:

首先,编写定时任务:

 1 import org.quartz.Job;
2 import org.quartz.JobExecutionContext;
3 import org.quartz.JobExecutionException;
4 import java.util.Date;
5
6 public class MyJob implements Job
7 {
8 public void execute(final JobExecutionContext jobExecutionContext) throws JobExecutionException
9 {
10 System.out.printf("%tc%n",new Date());
11 }
12 }

MyJob.java

其次,调用定时任务:

 1 import static org.quartz.JobBuilder.newJob;
2 import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
3 import static org.quartz.TriggerBuilder.newTrigger;
4 import static org.quartz.TriggerBuilder.*;
5 import static org.quartz.CronScheduleBuilder.*;
6 import static org.quartz.DateBuilder.*;
7 import org.quartz.JobDetail;
8 import org.quartz.Scheduler;
9 import org.quartz.SchedulerException;
10 import org.quartz.Trigger;
11 import org.quartz.impl.StdSchedulerFactory;
12
13 public class Test
14 {
15 public static void main(String[] args)
16 {
17 try {
18 Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
19
20 JobDetail job = newJob(MyJob.class) .withIdentity("job1", "group1") .build();
21
22 Trigger trigger = newTrigger()
23 .withIdentity("trigger1", "group1")
24 .startNow()
25 .withSchedule(
26 //注意与上面各cron expression不同,这里用2表示星期一
27 cronSchedule("0/1 * * ? * 2")
28 ).build();
29
30 scheduler.scheduleJob(job, trigger);
31
32 scheduler.start();
33
34 Thread.sleep(3000);
35
36 scheduler.shutdown();
37 }
38 catch (SchedulerException e)
39 {
40 e.printStackTrace();
41 }
42 catch (InterruptedException e)
43 {
44 e.printStackTrace();
45 }
46 }
47 }

Test.java

结果如下:

View Pic

总结:通过实验,发现除Quartz采用版本2外,其余环境均采用版本1。

建议

事实上,不同定时库采用的cron expression都是在最早的Unix cron中的cron expression上加入自己的小改动、小扩展形成的,因此彼此很可能会大同小异,所以给出以下建议:

  1. 在使用具体的库时最好仔细阅读相应的官方文档。
  2. 使用星期的英文缩写(如SUN,MON等,一般不区分大小写)代替数字来表示星期,这样既直观又能避免不同环境下数字表示星期的坑。

参考

[1] cron:维基百科中文
[2] cron & cron expression:维基百科英文
[3] Quartz中的cron expression。
[4] Oracle中的cron expression

写在后面

遗憾的是,国内许多博客(如这里这里)都直接采用版本2来介绍,却不指明是在什么样的环境中,这很容易让初学者(比如在下)以为世界上的cron expression只有一种,或者让先学过版本1的人(比如在下)产生深深的疑惑。所以,在下特地写文澄清。不为显摆,实在是不想再有人产生像在下之前那样的困惑。

另外还需说明的一点是,两个版本的cron expression还有其它一些区别,如版本2支持更多的特殊字符(如L、W、#等),且不允许在表示日和星期的位置上同时设置'*'。

由衷感谢本文中所有引文的作者。

本文验证材料可从这里获取,提取码: 4pkz,如链接失效,请您在评论区留言或私信,在下会及时更新。

由于在下才疏学浅,错误疏漏之处在所难免,欢迎读者批评指正,您的批评是在下前进的不竭动力。

cron表达式的双重人格:星期和数字到底如何对应?的更多相关文章

  1. 定时任务 Cron表达式

    Cron表达式由6~7项组成,中间用空格分开.从左到右依次是: 秒.分.时.日.月.周几.年(可省略) Cron表达式的值可以是数字,也可以是以下符号: "*":所有值都匹配 &q ...

  2. Quartz 和 springboot schedule中的cron表达式关于星期(周几)的不同表示

    一.Quartz中cron 表达式分析: quartz 官方源码(org.quartz.CronExpression)解释: Cron expressions are comprised of 6 r ...

  3. cron表达式

    Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,Cron有如下两种语法格式: Seconds Minutes Hours DayofMonth Month ...

  4. cron表达式详解[转]

    Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,Cron有如下两种语法格式: Seconds Minutes Hours DayofMonth Month ...

  5. cron表达式使用详解

    Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,Cron有如下两种语法格式: Seconds Minutes Hours DayofMonth Month ...

  6. Spring cron 表达式

    前言: 最近做的项目有用到定时器,每周只在特定时间运行一次,考虑到Spring Task的简单易用性,就果断选择了,我是配置在配置文件里面,没有用注解@Scheduled,推荐配置,注解虽方便,但更改 ...

  7. spring 计划任务:cron表达式

    Cron表达式是一个字符串,字符串以5或6个空格隔开,分开工6或7个域,每一个域代表一个含义,Cron有如下两种语法 格式: Seconds Minutes Hours DayofMonth Mont ...

  8. cron表达式详解(Spring定时任务配置时间间隔)

    Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,Cron有如下两种语法格式: Seconds Minutes Hours DayofMonth Month ...

  9. @Scheduled cron表达式

    一.Cron详解: Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,Cron有如下两种语法格式: 1.Seconds Minutes Hours Dayof ...

随机推荐

  1. Linux中Java开发常用的软件总结:

    开发工具下载: Tomcat下载:wget http://learning.happymmall.com/tomcat/apache-tomcat-7.0.73.tar.gzJDK下载: wget h ...

  2. Best Cow Line

    给定长度为N的字符串s,要构造一个长度为N的字符串T.起初,T是一个空串,随后反复进行下列任意操作. -从S的头部删除一个字符,加到T的尾部 -从S的尾部删除一个宇符,加到T的尾部 目标是要构造字典序 ...

  3. Hibernate框架基本使用

    时间:2017-1-16 00:36 --什么是Hibernate    Hibernate是一个开放源代码的关系映射框架,它对JDBC进行了非常轻量级的对象封装,使得Java程序员可以使用对象编程思 ...

  4. 原生 JS 与 jQuery 中的 AJAX

    AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML). AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更 ...

  5. 前端性能优化之http缓存

    前不久,公司前端开会,领导抽问了4个问题,前3个简单大家都答起来了,第4个问题关于缓存的这方面我只是了解,结果刚好问到我了(会的不问,专门挑我不熟悉的问,我这运气真是没话说),20多个前端看着我,答得 ...

  6. Kubernetes集群部署笔记

    本作品由Galen Suen采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可.由原作者转载自个人站点. 概述 本文用于整理基于Debian操作系统使用kubeadm工具部署Kub ...

  7. 基于Linux系统的MariaDB数据库的安装配置

    数据库是指长期存储在计算机内.有组织的和可共享的数据集合.表是数据库存储数据的基本单位,一个表由若干个字段组成 MariaDB 数据库管理系统是 MySQL 的一个分支,主要由开源社区在维护,采用 G ...

  8. tomcat配置启动不了

    关于ideatomcat配置问题 1.第一步配置tomcat启动器 2.配置启动的网址 3.配置启动器的启动 ---更多java学习,请见本人小博客:https://zhangjzm.gitee.io ...

  9. 面试官:Redis的事务满足原子性吗?

    原创:码农参上(微信公众号ID:CODER_SANJYOU),欢迎分享,转载请保留出处. 谈起数据库的事务来,估计很多同学的第一反应都是ACID,而排在ACID中首位的A原子性,要求一个事务中的所有操 ...

  10. SAR总结

    1.星载InSAR技术简介 星载合成孔径雷达干涉测量(InSAR)是一种用于大地测量和遥感的雷达技术.InSAR使用两个或多个SAR图像,利用返回卫星的波的相位差来计算目标地区的地形.地貌以及表面的微 ...