cronexpr
支持的比 Linux
自身的 crontab
更详细,可以精确到秒级别。
cronexpr
表达式从前到后的顺序如下所示:
字段类型 是否为必须字段 允许的值 允许的特殊字符
---------- ---------- -------------- --------------------------
Seconds No 0-59 * / , -
Minutes Yes 0-59 * / , -
Hours Yes 0-23 * / , -
Day of month Yes 1-31 * / , - L W
Month Yes 1-12 or JAN-DEC * / , -
Day of week Yes 0-6 or SUN-SAT * / , - L #
Year No 1970–2099 * / , -
我们定义第 0 个字段为 Seconds
,第 1 个字段为 Minutes
,各个字段的含义如下:
*
星号表示 cron
表达式与该字段的所有值匹配。例如,在第四个字段(月)中使用星号表示每个月;
/
斜线描述在该时间段的间隔。例如,分钟字段中的 3-59/15
表示每小时的第三分钟,此后每15分钟。*/...
的形式相当于 first-last/...
的形式,即在该字段的最大可能范围内的增量;
,
逗号用于分隔列表中的项目。例如,在第 5 个字段(星期)中使用 MON,WED,FRI
表示星期一、星期三和星期五;
-
连字符定义范围。例如,2000-2010
表示公元 2000 年至 2010 年之间的每一年,包括在内,为闭区间;
L
代表 “最后”。当在周天字段中使用时,它允许你指定诸如 “最后一个星期五”(5L
)这样的结构。在月日字段中,它指定了该月的最后一天;
W
月日字段允许使用 W
字符。这个字符用于指定离给定日期最近的工作日(周一至周五)。举个例子,如果你指定 15W
作为月日字段的值,其含义是:“离本月 15 日最近的工作日”。所以,如果 15 号是星期六,触发器在 14 号星期五启动。如果 15 号是星期天,触发器在 16 号星期一启动。如果 15 号是星期二,那么它就在 15 号星期二触发。但是,如果你指定 1W
作为月日的值,并且 1 号是星期六,那么触发器在 3 号星期一发射,因为它不会 "跳过 "一个月的天数边界。
只有当月日是一个单一的日子,而不是一个范围或日期列表时,才能指定 W
字符。W
字符也可以与 L
结合使用,如 LW
表示 “本月的最后一个工作日”。
#
允许在星期字段中使用 #
,后面必须有一个 1 到 5 之间的数字。它允许你指定一些结构,如某个月的 “第二个星期五”。
Entry Description Equivalent to
@annually Run once a year at midnight in the morning of January 1 0 0 0 1 1 * *
@yearly Run once a year at midnight in the morning of January 1 0 0 0 1 1 * *
@monthly Run once a month at midnight in the morning of the first of the month 0 0 0 1 * * *
@weekly Run once a week at midnight in the morning of Sunday 0 0 0 * * 0 *
@daily Run once a day at midnight 0 0 0 * * * *
@hourly Run once an hour at the beginning of the hour 0 0 * * * * *
@reboot Not supported
* * * * * 2013
在内部成为 0 * * * * 2013
;* * * * Mon
内部成为 0 * * * Mon *
;[0-7]
,而不是 [0-6]
,7 是星期天(像0)。这是为了符合http://linux.die.net/man/5/crontab#;go get github.com/gorhill/cronexpr
expr, err := cronexpr.Parse("*/5 * * * * * * ") // 如果表达式解析错误将返回一个错误
expr = cronexpr.MustParse("*/5 * * * * * * ") // 如果表达式解析错误将直接抛出 panic
package main
import (
"fmt"
"time"
"github.com/gorhill/cronexpr"
)
func doTask() {
fmt.Println("I am running, time is: ", time.Now())
}
func main() {
// 每隔 5 秒执行1次
expr, err := cronexpr.Parse("*/5 * * * * * * ") // 如果表达式解析错误将返回一个错误
if err != nil {
fmt.Println(err)
return
}
nextTime := expr.Next(time.Now())
fmt.Println(nextTime)
time.AfterFunc(time.Until(nextTime), doTask)
time.Sleep(10 * time.Second)
}
package main
import (
"fmt"
"time"
"github.com/gorhill/cronexpr"
)
// 代表一个任务
type CronJob struct {
expr *cronexpr.Expression
nextTime time.Time // expr.Next(now)
}
func main() {
// 定时任务字典, key: 任务的名字, value 任务对象
scheduleTable := make(map[string]*CronJob)
now := time.Now()
// 定义定时任务以每 5s 执行一次
// MustParse 如果遇到解析 contab 错误时会直接抛出 panic ,不会像 Parse 一样返回一个错误
expr := cronexpr.MustParse("*/5 * * * * * *")
cronJob := &CronJob{
expr: expr,
nextTime: expr.Next(now),
}
// 任务注册到调度表
scheduleTable["job1"] = cronJob
// 定义定时任务以每 3s 执行一次
expr = cronexpr.MustParse("*/3 * * * * * *")
cronJob = &CronJob{
expr: expr,
nextTime: expr.Next(now),
}
// 任务注册到调度表
scheduleTable["job2"] = cronJob
// 定时检查一下任务调度表
for {
now := time.Now()
for jobName, cronJob := range scheduleTable {
// 判断是否到期,当前时间等于定时任务的下次执行时间,或者当前时间大于任务的定时时间
if now.Equal(cronJob.nextTime) || now.After(cronJob.nextTime) {
// 启动一个协程, 执行这个任务
go func(jobName string) {
fmt.Println("执行:", jobName)
}(jobName)
// 计算下一次调度时间
cronJob.nextTime = cronJob.expr.Next(now)
fmt.Println(jobName, "下次执行时间:", cronJob.nextTime)
}
}
// 等待 1s,减少 CPU 消耗
t := <-time.NewTimer(1 * time.Second).C
fmt.Println(t)
}
}