min hour day month week cmd
crontab表达式描述的是时间集合 */1 * * * *
表示所有的分钟集合(0-59)都会执行
字段名 | 是否必须 | 允许的值 | 允许的特定字符 |
分(Minutes) | 是 | 0-59 | * / , - |
时(Hours) | 是 | 0-23 | * / , - |
日(Day of month)/td> | 是 | 1-31 | * / , – ? |
月(Month) | 是 | 1-12 or JAN-DEC | * / , - |
星期(Day of week) | 否 | 0-6 or SUM-SAT | * / , – ? |
先那分钟(min)这个栏位来说,我们用 int64 的二进制来标记分钟上的集合,从右往左第1位标识0分钟,第2位标记标识1分钟一直到第60位标记第59分钟.
用程序语言描述的话就是 第0分钟用 1<< 0 标识 第1分钟用 1<<1 标记 第59分钟就是 1<< 59。
ok,照上面说的方法 我们如何表示 */1 * * * *
分钟的所有集合呢?其实 “/”的右边表示集合范围,右边表示步长(step), 我们都可以用下面的算法算出他的标识方式
for i := min; i <= max; i += step {
bits |= 1 << i
}
表示这个栏位最大的集合,上面的表达式
其实就是 1 << 0 | 1 <<1 | 1 <<2 ... 1<< 59
等同于 ^(MaxUint64 << 60)
等于
二进制 ( 111111111111111111111111111111111111111111111111111111111111)
那么同理 */2 * * * *
标识被2整除的集合就是 1<< 0 | 1<< 2 | 1<<4 .. 1<< 58 等于二进制
(010101010101010101010101010101010101010101010101010101010101)
。
crontab 表达式中 ‘/’ 后面的数值是步长(step) 可执行的时刻值可以理解为 范围内除以 步长 余数为 0 的时刻
例如*/7 * * * *
crontab 在 并不是严格的每7分钟执行一次,他的二进制表示方法如下:
000100000010000001000000100000010000001000000100000010000001
从这个表达式中可以看出 /7 * * * 他的执行分钟时刻为0 7 14 21 28 35 42 49 56 下一个执行时间是 下一个小时的 0分钟 (并不是下个小时的1分钟)
“-”表示一个范围 2-9/2 * * * * 这种表达式 就是 2到9分钟 被2整除的所有集合 就是 2 4 6 8 表示为 1<<2 | 1 <<4 |1<< 6 | 1<< 8
同样它的标识方式时
for i := 2; i <= 9; i += 2 {
bits |= 1 << i
}
“,”表示枚举值 比如表达式 1,5,9 * * * * 他表示的时1分钟,5分钟和9分钟 1<< 1 | 1<< 5 | 1<< 9
“?”只用于 日(Day of month) 和 星期(Day of week),表示不指定值,可以用于代替 *
分钟的标识方式已经清楚了,其它的四个栏位标识方式和它一样,唯一有差别的时 栏位的集合范围,比如 分钟是 0-59 小时是 0-23
完整表达式 “1-9/2 8-23 1,3,5 9 ?”
最后这个表达式的标识方式如下:
min : 1010101010
hour : 111111111111111100000000
day : 101010
month : 1000000000
week : 1111111
首先看一下cron的结构体
type Cron struct {
Schedule Schedule //时间集合
Job job // cmd
Next time.Time //下次执行时间
Prev time.Time //上次执行时间
}
所以遍历的程序看起来像
for i=0;i
获取到Next的是之后 我们需要对Cron进行按照Next 从小到大排序(最近要执行的排在最前面)
从排过序的列表中拿到第一个cron 的 Next值,程序阻塞等Next时刻的时候执行(当然 需要拿到列表中相同Next的任务 并发执行 而且不能阻塞主进程)
主程序继续对任务列表计算Next值 重复 上面3个步骤。
ok,到了这边 主线流程逐渐清晰了。还剩下 calculation 这个函数的实现没说明,继续外下看。
calculation 计算的是cron时间集合中最近要执行的时刻,那么如何具体如何计算的,
在cron 时间集合里头的 month 里头找到离当前时间最近的月份,然后离当前时间最近的天,然后离当前时间最近的小时 然后离当前时间最近的分钟 是不是就可以了
ok 按照这个思路 程序看起来是这个样子
t = time.Now()
while( 1 << t.month() & Schedule.month == 0){
t.month = t.month +1 // 当前时间月份加1
}
#day 栏位比较特别 week实际上也是作用于天的,day 表示的是月份的天,所以对天数加的+1的时候 需要同时匹配day 和 week 所以程序
while (!(1<
下面hour min 类似,最后通过各种+ 操作的 t 就是这个job最近的时刻了。