robfiig/cron 源码剖析
Cron 表达式
参考wiki
https://en.wikipedia.org/wiki/Cron
robfiig/cron项目信息
下载地址
https://github.com/robfig/cron.git
文件目录讲解
constantdelay.go #一个最简单的秒级别定时系统。与cron无关
constantdelay_test.go #测试
cron.go #Cron系统。管理一系列的cron定时任务(Schedule Job)
cron_test.go #测试
doc.go #说明文档
LICENSE #授权书
parser.go #解析器,解析cron格式字符串城一个具体的定时器(Schedule)
parser_test.go #测试
README.md #README
spec.go #单个定时器(Schedule)结构体。如何计算自己的下一次触发时间
spec_test.go #测试
robfiig/cron 目前实现的需求
doc.go
CRON Expression Format
A cron expression represents a set of times, using 6 space-separated fields.
Field name | Mandatory? | Allowed values | Allowed special characters
---------- | ---------- | -------------- | --------------------------
Seconds | Yes | 0-59 | * / , -
Minutes | Yes | 0-59 | * / , -
Hours | Yes | 0-23 | * / , -
Day of month | Yes | 1-31 | * / , - ?
Month | Yes | 1-12 or JAN-DEC | * / , -
Day of week | Yes | 0-6 or SUN-SAT | * / , - ?
Predefined schedules
You may use one of several pre-defined schedules in place of a cron expression.
Entry | Description | Equivalent To
----- | ----------- | -------------
@yearly (or @annually) | Run once a year, midnight, Jan. 1st | 0 0 0 1 1 *
@monthly | Run once a month, midnight, first of month | 0 0 0 1 * *
@weekly | Run once a week, midnight on Sunday | 0 0 0 * * 0
@daily (or @midnight) | Run once a day, midnight | 0 0 0 * * *
@hourly | Run once an hour, beginning of hour | 0 0 * * * *
可以看到这里并没有实现 L , W , #
这些特殊字符。 至于原因,下面将具体实现代码的时候会给出。
cron表达式解析
cron 的BNF表达式以及引出的解析方式
首先,让我试着使用EBNF来定义下cron 表达式(不是很严谨。。。)
<non_zero> ::= "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
<num> ::= <non_zero> | "0"
<number> ::= <non_zero> {<num>}
<normal_item> ::= "*" | <number> | <number> "-"<number> | <number> "/" <number>
<days_item> ::= <normal_item> | "?" | <number>"L" | <number>"W" | <number>"#"
<item> ::= <normal_item> | <days_item>;
<items> ::= {<item>","} <item> ;
<cron> ::= <items>" "<items>" "<items>" "<items>" "<items>" "<items>
至此, 如果我打算解析一个cron表达式, 应该遵循以下步骤 :
cron
利用空白拆解出独立的items
。items
利用,
拆解出item
。item
利用穷举法一一检测( 符合且仅符合下面的一条才能合法) :
- 是否仅有一个字符是
*
或者?
。 - 是否可以使用
/
或者-
分解为俩数字 。 - 是否以数字加
L
或者W
结尾。 - 纯数字。
- 是否仅有一个字符是
- 将规则一一描述,完成规则解析
如何表示一个独立的规则
cron表达式是用来表示一系列时间的,而时间是无法逃脱自己的区间的 , 分,秒 0 - 59 , 时 0 - 23 , 天/月 0 - 31 , 天/周 0 - 6 , 月0 - 11 。 这些本质上都是一个点集合,或者说是一个整数区间。 那么对于任意的整数区间 , 可以描述cron的如下部分规则。
* | ?
任意 , 对应区间上的所有点。 ( 额外注意 日/周 , 日 / 月 的相互干扰。)- 纯数字 , 对应一个具体的点。
/
分割的两个数字 a , b, 区间上符合 a + n * b 的所有点 ( n >= 0 )。-
分割的两个数字, 对应这两个数字决定的区间内的所有点。L | W
需要对于特定的时间特殊判断, 无法通用的对应到区间上的点。
至此, robfig/cron
为什么不支持 L | W
的原因已经明了了。去除这两条规则后, 其余的规则其实完全可以使用点的穷举来通用表示。 考虑到最大的区间也不过是60个点,那么使用一个uint64
的整数的每一位来表示一个点便很合适了。下面是robfig/cron
的表示方法 :
/*
------------------------------------------------------------
第64位标记任意 , 用于 日/周 , 日 / 月 的相互干扰。
63 - 0 为 表示区间 [63 , 0] 的 每一个点。
------------------------------------------------------------
假设区间是 0 - 63 , 则有如下的例子 :
比如 0/3 的表示如下 :
* / ?
+---+--------------------------------------------------------+
| 0 | 1 0 0 1 0 0 1 ~~ ~~ 1 0 0 1 0 0 1 |
+---+--------------------------------------------------------+
63 ~ ~ ~~ 0
比如 2-5 的表示如下 :
* / ?
+---+--------------------------------------------------------+
| 0 | 0 0 0 0 ~ ~ ~~ ~ 0 0 0 1 1 1 1 0 0 |
+---+--------------------------------------------------------+
63 ~ ~ ~~ 0
比如 * 的表示如下 :
* / ?
+---+--------------------------------------------------------+
| 1 | 1 1 1 1 1 ~ ~ ~ 1 1 1 1 1 1 1 1 1 |
+---+--------------------------------------------------------+
63 ~ ~ ~~ 0
*/
定时器的基本功能
一个时间是否符合规则
有一个规则后, 判断一个时间点是否符合规则其实就是对应位是否为1 。
预判断下一个符合规则的时间
给定一个时间后, 寻找下一个符合符合规则的时间也很简单 :
- 从大到小,依次