一些关于时间和定时任务的库

避免重复发明轮子。如果有一些好用的库,我们就直接使用就好了,没必要做一些重复的工作,如果这些库不能满足需求,不妨提交pull request或者clone 它们,提升它们,优化它们,当前前提是你得知道它们。

这篇文章给大家介绍一些关于时间和类似linux cron功能的定时任务库。

jinzhu/now

张金柱大佬除了给大家贡献了gorm外,还写了一些好用的Go库, jinzhu/now就是之一。

当前,Go标准库提供了关于时间的库time,可以满足我们80%的场景,但是对于一些特殊的场景,使用标准库却很麻烦,其它一些编程语言也是这样,标准库中的时间相关的函数在一些特殊场景下不方便使用,金柱提供的now库满足了一些特殊场景的需求,使用起来特别的方便。

这个库最重要的函数我把它分成两类:

  • 计算第一天和最后一天的函数
  • 解析

当然它还包含求四季,以及明确求周日周一的函数(有的国家和地区星期天算一周的开始、有的周一算一周的开始),这更少用了,我们就不介绍了。

计算开始和最后时刻的函数

给定一个时间,你可以得到这个时刻的此分钟开始的时刻、此小时开始的时刻、此天的开始的时刻、此周开始的时刻、此月开始的时刻、此季节开始的时刻、此半年开始的时刻,此年开始的时刻:

1
2
3
4
5
6
7
8
9
10
11
import "github.com/jinzhu/now"
time.Now() // 2013-11-18 17:51:49.123456789 Mon
now.BeginningOfMinute() // 2013-11-18 17:51:00 Mon
now.BeginningOfHour() // 2013-11-18 17:00:00 Mon
now.BeginningOfDay() // 2013-11-18 00:00:00 Mon
now.BeginningOfWeek() // 2013-11-17 00:00:00 Sun
now.BeginningOfMonth() // 2013-11-01 00:00:00 Fri
now.BeginningOfQuarter() // 2013-10-01 00:00:00 Tue
now.BeginningOfYear() // 2013-01-01 00:00:00 Tue

或者这个时刻的此分钟最后的时刻、此小时最后的时刻、此天的最后的时刻、此周最后的时刻、此月最后的时刻、此季节最后的时刻、此半年最后的时刻,此年最后的时刻:

1
2
3
4
5
6
7
8
9
10
now.EndOfMinute() // 2013-11-18 17:51:59.999999999 Mon
now.EndOfHour() // 2013-11-18 17:59:59.999999999 Mon
now.EndOfDay() // 2013-11-18 23:59:59.999999999 Mon
now.EndOfWeek() // 2013-11-23 23:59:59.999999999 Sat
now.EndOfMonth() // 2013-11-30 23:59:59.999999999 Sat
now.EndOfQuarter() // 2013-12-31 23:59:59.999999999 Tue
now.EndOfYear() // 2013-12-31 23:59:59.999999999 Tue
now.WeekStartDay = time.Monday // 设置 Monday 作为每周的第一天, 默认星期天是一周的第一天
now.EndOfWeek() // 2013-11-24 23:59:59.999999999 Sun

如果你是求当前时刻的开始时刻和结束时刻,你可以使用包的函数,它提供了便利的函数,比如当前小时的开始时刻:

1
_ = time.BeginningOfHour()

它实际的实现是:

1
2
3
func BeginningOfHour() time.Time {
return With(time.Now()).BeginningOfHour()
}

你还可以设置特定的时区、日期格式和每周的开始的第一天是星期一还是星期天:

1
2
3
4
5
6
7
8
myConfig := &now.Config{
WeekStartDay: time.Monday,
TimeLocation: location,
TimeFormats: []string{"2006-01-02 15:04:05"},
}
t := time.Date(2013, 11, 18, 17, 51, 49, 123456789, time.Now().Location()) // // 2013-11-18 17:51:49.123456789 Mon
myConfig.With(t).BeginningOfWeek() // 2013-11-18 00:00:00 Mon

日期解析

标注库的日期解析方式是以样本的方式提供的,比如2006-01-02 15:04:05,这种解析方法比较特殊,经常我们需要查看帮助文档才能正确设置想起的格式,而且解析的时候容错能力不是太好。

jizhu/now库提供了一种容错式的解析方式,它会遍历标准库的时间格式,尝试使用其中的一种格式进行解析。它的处理方式有遍历和正则表达式,所以如果是在追求性能和日期格式比较明确的情况下,用标准库的解析就好,但是如果不追求极致的性能,这个解析能很好的容错,你不需要记住格式模板,输入一个日期字符串它就能解析。(哦觉得它还可以做一个优化,除了标准库提供的日期格式外,允许用户提供特定的日期格式模板,并且设定优先级)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Parse(string) (time.Time, error)
t, err := now.Parse("2017") // 2017-01-01 00:00:00, nil
t, err := now.Parse("2017-10") // 2017-10-01 00:00:00, nil
t, err := now.Parse("2017-10-13") // 2017-10-13 00:00:00, nil
t, err := now.Parse("1999-12-12 12") // 1999-12-12 12:00:00, nil
t, err := now.Parse("1999-12-12 12:20") // 1999-12-12 12:20:00, nil
t, err := now.Parse("1999-12-12 12:20:21") // 1999-12-12 12:20:21, nil
t, err := now.Parse("10-13") // 2013-10-13 00:00:00, nil
t, err := now.Parse("12:20") // 2013-11-18 12:20:00, nil
t, err := now.Parse("12:20:13") // 2013-11-18 12:20:13, nil
t, err := now.Parse("14") // 2013-11-18 14:00:00, nil
t, err := now.Parse("99:99") // 2013-11-18 12:20:00, Can't parse string as time: 99:99
// MustParse must parse string to time or it will panic
now.MustParse("2013-01-13") // 2013-01-13 00:00:00
now.MustParse("02-17") // 2013-02-17 00:00:00
now.MustParse("2-17") // 2013-02-17 00:00:00
now.MustParse("8") // 2013-11-18 08:00:00
now.MustParse("2002-10-12 22:14") // 2002-10-12 22:14:00
now.MustParse("99:99") // panic: Can't parse string as time: 99:99

carbon

carbon是另外一个日期/时间扩展库。很多语言都有一个兼做carbon的日期扩展库,比如javascript、php、python、rust等等。Go语言这也不止一个,这里我介绍的是golang-module/carbon
golang-module/carbon是一个轻量级、语义化、对开发者友好的 golang 时间处理库,支持链式调用,由够过瘾开发。

它提供了非常丰富的函数,看的我老眼昏花。它提供的函数大致分为几类。

创建carbon实例

根据参数的不同,创建的方式很多,下面只是列出了几种创建的方法:

1
2
3
4
carbon.CreateFromTimestamp(0).ToString() // 1970-01-01 08:00:00 +0800 CST
carbon.CreateFromTimestampMilli(1649735755981).ToString() // 2022-04-12 11:55:55.981 +0800 CST
carbon.CreateFromDate(2020, 8, 5).ToString() // // 2020-08-05 13:14:15 +0800 CST
carbon.CreateFromTime(13, 14, 15).ToString() // 2020-08-05 13:14:15 +0800 CST

和标准库互转:

1
2
3
4
// 将 time.Time 转换成 Carbon
carbon.Time2Carbon(time.Now())
// 将 Carbon 转换成 time.Time
carbon.Now().Carbon2Time()

昨天、今天和明天, 以及转换成字符串,下面是一部分例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 今天此刻
fmt.Sprintf("%s", carbon.Now()) // 2020-08-05 13:14:15
carbon.Now().ToString() // 2020-08-05 13:14:15 +0800 CST
carbon.Now().ToDateTimeString() // 2020-08-05 13:14:15
// 今天日期
carbon.Now().ToDateString() // 2020-08-05
// 今天时间
carbon.Now().ToTimeString() // 13:14:15
// 昨天此刻
fmt.Sprintf("%s", carbon.Yesterday()) // 2020-08-04 13:14:15
carbon.Yesterday().ToString() // 2020-08-04 13:14:15 +0800 CST
// 明天此刻
fmt.Sprintf("%s", carbon.Tomorrow()) // 2020-08-06 13:14:15
carbon.Tomorrow().ToString() // 2020-08-06 13:14:15 +0800 CST
carbon.Tomorrow().ToDateTimeString() // 2020-08-06 13:14:15

一些字符串格式例子:

1
2
3
4
5
6
7
8
// 输出日期时间字符串
carbon.Parse("2020-08-05T13:14:15.999999999+08:00").ToDateTimeString() // 2020-08-05 13:14:15
// 输出日期时间字符串,包含毫秒
carbon.Parse("2020-08-05T13:14:15.999999999+08:00").ToDateTimeMilliString() // 2020-08-05 13:14:15.999
// 输出日期时间字符串,包含微秒
carbon.Parse("2020-08-05T13:14:15.999999999+08:00").ToDateTimeMicroString() // 2020-08-05 13:14:15.999999
// 输出日期时间字符串,包含纳秒
carbon.Parse("2020-08-05T13:14:15.999999999+08:00").ToDateTimeNanoString() // 2020-08-05 13:14:15.999999999

解析

carbon解析采用标准格式或者模板格式,标准格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
carbon.Parse("now").ToString() // 2020-08-05 13:14:15 +0800 CST
carbon.Parse("yesterday").ToString() // 2020-08-04 13:14:15 +0800 CST
carbon.Parse("tomorrow").ToString() // 2020-08-06 13:14:15 +0800 CST
carbon.Parse("2020").ToString() // 2020-01-01 00:00:00 +0800 CST
carbon.Parse("2020-8").ToString() // 2020-08-01 00:00:00 +0800 CST
carbon.Parse("2020-08").ToString() // 2020-08-01 00:00:00 +0800 CST
carbon.Parse("2020-8-5").ToString() // 2020-08-05 00:00:00 +0800 CST
carbon.Parse("2020-8-05").ToString() // 2020-08-05 00:00:00 +0800 CST
carbon.Parse("2020-08-05").ToString() // 2020-08-05 00:00:00 +0800 CST
carbon.Parse("2020-08-05.999").ToString() // 2020-08-05 00:00:00.999 +0800 CST
carbon.Parse("2020-08-05.999999").ToString() // 2020-08-05 00:00:00.999999 +0800 CST
carbon.Parse("2020-08-05.999999999").ToString() // 2020-08-05 00:00:00.999999999 +0800 CST
carbon.Parse("2020-8-5 13:14:15").ToString() // 2020-08-05 13:14:15 +0800 CST
carbon.Parse("2020-8-05 13:14:15").ToString() // 2020-08-05 13:14:15 +0800 CST
carbon.Parse("2020-08-05T13:14:15.999999999+08:00").ToString() // 2020-08-05 13:14:15.999999999 +0800 CST
carbon.Parse("20200805").ToString() // 2020-08-05 00:00:00 +0800 CST
carbon.Parse("20200805131415.999999999+08:00").ToString() // 2020-08-05 13:14:15.999999999 +0800 CST

模板格式:

1
2
3
4
carbon.ParseByFormat("2020|08|05 13|14|15", "Y|m|d H|i|s").ToDateTimeString() // 2020-08-05 13:14:15
carbon.ParseByFormat("It is 2020-08-05 13:14:15", "\\I\\t \\i\\s Y-m-d H:i:s").ToDateTimeString() // 2020-08-05 13:14:15
carbon.ParseByFormat("今天是 2020年08月05日13时14分15秒", "今天是 Y年m月d日H时i分s秒").ToDateTimeString() // 2020-08-05 13:14:15
carbon.ParseByFormat("2020-08-05 13:14:15", "Y-m-d H:i:s", carbon.Tokyo).ToDateTimeString() // 2020-08-05 14:14:15

或者Go标准库布局模式:

1
2
3
4
carbon.ParseByLayout("2020|08|05 13|14|15", "2006|01|02 15|04|05").ToDateTimeString() // 2020-08-05 13:14:15
carbon.ParseByLayout("It is 2020-08-05 13:14:15", "It is 2006-01-02 15:04:05").ToDateTimeString() // 2020-08-05 13:14:15
carbon.ParseByLayout("今天是 2020年08月05日13时14分15秒", "今天是 2006年01月02日15时04分05秒").ToDateTimeString() // 2020-08-05 13:14:15
carbon.ParseByLayout("2020-08-05 13:14:15", "2006-01-02 15:04:05", carbon.Tokyo).ToDateTimeString() // 2020-08-05 14:14:15

开始时刻和结束时刻

和jizhu/now的功能类似,求一个时刻开始点和结束点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 本世纪开始时间
carbon.Parse("2020-08-05 13:14:15").StartOfCentury().ToDateTimeString() // 2000-01-01 00:00:00
// 本世纪结束时间
carbon.Parse("2020-08-05 13:14:15").EndOfCentury().ToDateTimeString() // 2999-12-31 23:59:59
// 本年代开始时间
carbon.Parse("2020-08-05 13:14:15").StartOfDecade().ToDateTimeString() // 2020-01-01 00:00:00
carbon.Parse("2021-08-05 13:14:15").StartOfDecade().ToDateTimeString() // 2020-01-01 00:00:00
carbon.Parse("2029-08-05 13:14:15").StartOfDecade().ToDateTimeString() // 2020-01-01 00:00:00
// 本年代结束时间
carbon.Parse("2020-08-05 13:14:15").EndOfDecade().ToDateTimeString() // 2029-12-31 23:59:59
carbon.Parse("2021-08-05 13:14:15").EndOfDecade().ToDateTimeString() // 2029-12-31 23:59:59
carbon.Parse("2029-08-05 13:14:15").EndOfDecade().ToDateTimeString() // 2029-12-31 23:59:59
// 本年开始时间
carbon.Parse("2020-08-05 13:14:15").StartOfYear().ToDateTimeString() // 2020-01-01 00:00:00
// 本年结束时间
carbon.Parse("2020-08-05 13:14:15").EndOfYear().ToDateTimeString() // 2020-12-31 23:59:59
// 本季度开始时间
carbon.Parse("2020-08-05 13:14:15").StartOfQuarter().ToDateTimeString() // 2020-07-01 00:00:00
// 本季度结束时间
carbon.Parse("2020-08-05 13:14:15").EndOfQuarter().ToDateTimeString() // 2020-09-30 23:59:59
......
// 本分钟开始时间
carbon.Parse("2020-08-05 13:14:15").StartOfMinute().ToDateTimeString() // 2020-08-05 13:14:00
// 本分钟结束时间
carbon.Parse("2020-08-05 13:14:15").EndOfMinute().ToDateTimeString() // 2020-08-05 13:14:59
// 本秒开始时间
carbon.Parse("2020-08-05 13:14:15").StartOfSecond().ToString() // 2020-08-05 13:14:15 +0800 CST
// 本秒结束时间
carbon.Parse("2020-08-05 13:14:15").EndOfSecond().ToString() // 2020-08-05 13:14:15.999999999 +0800 CST

好吧,向上它已经提供劳务世纪初和世纪末的时间点、已经秒级别的开始时间点和结束时间点。

时间旅行

提供了时间时移的功能,增加时间或者减少时间到另外一个时间点。

依然很大气哦,可以提供世纪级别的方法纳秒级别的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 三个世纪后
carbon.Parse("2020-02-29 13:14:15").AddCenturies(3).ToDateTimeString() // 2320-02-29 13:14:15
// 三个世纪后(月份不溢出)
carbon.Parse("2020-02-29 13:14:15").AddCenturiesNoOverflow(3).ToDateTimeString() // 2320-02-29 13:14:15
......
// 三个世纪后
carbon.Parse("2020-02-29 13:14:15").AddCenturies(3).ToDateTimeString() // 2320-02-29 13:14:15
// 三个世纪后(月份不溢出)
carbon.Parse("2020-02-29 13:14:15").AddCenturiesNoOverflow(3).ToDateTimeString() // 2320-02-29 13:14:15
......
// 三纳秒后
carbon.Parse("2020-08-05 13:14:15.222222222").AddNanoseconds(3).ToString() // 2020-08-05 13:14:15.222222225 +0800 CST
// 一纳秒后
carbon.Parse("2020-08-05 13:14:15.222222222").AddNanossecond().ToString() // 2020-08-05 13:14:15.222222223 +0800 CST
// 三纳秒前
carbon.Parse("2020-08-05 13:14:15.222222222").SubNanosseconds(3).ToString() // 2020-08-05 13:14:15.222222219 +0800 CST
// 一纳秒前
carbon.Parse("2020-08-05 13:14:15.222222222").SubNanossecond().ToString() // 2020-08-05 13:14:15.222222221 +0800 CST

仁者见仁智者见智,有些人喜欢这种细粒度的方法,挺好。不过看到这么多的方法,提供类似的功能,我会提供一个方法,然后第二个单位用枚举类型来指定,这样一个时移通过一个方法就搞定了,这样学习起来和维护起来比较方便,比如我会设计成这样:

1
2
carbon.Parse("2020-02-29 13:14:15").Add(3, carbon.Century)
carbon.Parse("2020-02-29 13:14:15").Add(3, carbon.Nano)

你喜欢哪种方法,欢迎评论区写出你的想法。

时间差

求两个时间的差值,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 相差多少年
carbon.Parse("2021-08-05 13:14:15").DiffInYears(carbon.Parse("2020-08-05 13:14:15")) // -1
// 相差多少年(绝对值)
carbon.Parse("2021-08-05 13:14:15").DiffAbsInYears(carbon.Parse("2020-08-05 13:14:15")) // 1
...
/ 相差多少秒
carbon.Parse("2020-08-05 13:14:15").DiffInSeconds(carbon.Parse("2020-08-05 13:14:14")) // -1
// 相差多少秒(绝对值)
carbon.Parse("2020-08-05 13:14:15").DiffAbsInSeconds(carbon.Parse("2020-08-05 13:14:14")) // 1
// 相差字符串
carbon.Now().DiffInString() // just now
carbon.Now().AddYearsNoOverflow(1).DiffInString() // -1 year
carbon.Now().SubYearsNoOverflow(1).DiffInString() // 1 year
// 相差字符串(绝对值)
carbon.Now().DiffAbsInString(carbon.Now()) // just now
carbon.Now().AddYearsNoOverflow(1).DiffAbsInString(carbon.Now()) // 1 year
carbon.Now().SubYearsNoOverflow(1).DiffAbsInString(carbon.Now()) // 1 year

一些时间判断

比如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
carbon.Parse("0").IsZero() // true
carbon.Parse("0000-00-00 00:00:00").IsZero() // true
carbon.Parse("0").IsZero() // true
carbon.Parse("0000-00-00 00:00:00").IsZero() // true
// 是否是闰年
carbon.Parse("2020-08-05 13:14:15").IsLeapYear() // true
// 是否是长年
carbon.Parse("2020-08-05 13:14:15").IsLongYear() // true
// 是否是一月
carbon.Parse("2020-08-05 13:14:15").IsJanuary() // false
// 是否是闰年
carbon.Parse("2020-08-05 13:14:15").IsLeapYear() // true
// 是否是长年
carbon.Parse("2020-08-05 13:14:15").IsLongYear() // true
// 是否是一月
carbon.Parse("2020-08-05 13:14:15").IsJanuary() // false

设置时间的某个单位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/ 设置区域
carbon.Parse("2020-07-05 13:14:15").SetLocale("en").DiffForHumans() // 1 month ago
carbon.Parse("2020-07-05 13:14:15").SetLocale("zh-CN").DiffForHumans() // 1 月前
// 设置年月日时分秒
carbon.Parse("2020-01-01").SetDateTime(2019, 2, 2, 13, 14, 15).ToString() // 2019-02-02 13:14:15 +0800 CST
carbon.Parse("2020-01-01").SetDateTime(2019, 2, 31, 13, 14, 15).ToString() // 2019-03-03 13:14:15 +0800 CST
// 设置年月日时分秒毫秒
carbon.Parse("2020-01-01").SetDateTimeMilli(2019, 2, 2, 13, 14, 15, 999).ToString() // 2019-02-02 13:14:15.999 +0800 CST
carbon.Parse("2020-01-01").SetDateTimeMilli(2019, 2, 31, 13, 14, 15, 999).ToString() // 2019-03-03 13:14:15.999 +0800 CST
// 设置年月日时分秒微秒
carbon.Parse("2020-01-01").SetDateTimeMicro(2019, 2, 2, 13, 14, 15, 999999).ToString() // 2019-02-02 13:14:15.999999 +0800 CST
carbon.Parse("2020-01-01").SetDateTimeMicro(2019, 2, 31, 13, 14, 15, 999999).ToString() // 2019-03-03 13:14:15.999999 +0800 CST
// 设置年月日时分秒纳秒
carbon.Parse("2020-01-01").SetDateTimeNano(2019, 2, 2, 13, 14, 15, 999999999).ToString() // 2019-02-02 13:14:15.999999999 +0800 CST
carbon.Parse("2020-01-01").SetDateTimeNano(2019, 2, 31, 13, 14, 15, 999999999).ToString() // 2019-03-03 13:14:15.999999999 +0800 CST

获取时间的某个单位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/ 设置区域
carbon.Parse("2020-07-05 13:14:15").SetLocale("en").DiffForHumans() // 1 month ago
carbon.Parse("2020-07-05 13:14:15").SetLocale("zh-CN").DiffForHumans() // 1 月前
// 设置年月日时分秒
carbon.Parse("2020-01-01").SetDateTime(2019, 2, 2, 13, 14, 15).ToString() // 2019-02-02 13:14:15 +0800 CST
carbon.Parse("2020-01-01").SetDateTime(2019, 2, 31, 13, 14, 15).ToString() // 2019-03-03 13:14:15 +0800 CST
// 设置年月日时分秒毫秒
carbon.Parse("2020-01-01").SetDateTimeMilli(2019, 2, 2, 13, 14, 15, 999).ToString() // 2019-02-02 13:14:15.999 +0800 CST
carbon.Parse("2020-01-01").SetDateTimeMilli(2019, 2, 31, 13, 14, 15, 999).ToString() // 2019-03-03 13:14:15.999 +0800 CST
// 设置年月日时分秒微秒
carbon.Parse("2020-01-01").SetDateTimeMicro(2019, 2, 2, 13, 14, 15, 999999).ToString() // 2019-02-02 13:14:15.999999 +0800 CST
carbon.Parse("2020-01-01").SetDateTimeMicro(2019, 2, 31, 13, 14, 15, 999999).ToString() // 2019-03-03 13:14:15.999999 +0800 CST
// 设置年月日时分秒纳秒
carbon.Parse("2020-01-01").SetDateTimeNano(2019, 2, 2, 13, 14, 15, 999999999).ToString() // 2019-02-02 13:14:15.999999999 +0800 CST
carbon.Parse("2020-01-01").SetDateTimeNano(2019, 2, 31, 13, 14, 15, 999999999).ToString() // 2019-03-03 13:14:15.999999999 +0800 CST
// 获取当前区域
carbon.Now().Locale() // en
carbon.Now().SetLocale("zh-CN").Locale() // zh-CN
// 获取当前星座
carbon.Now().Constellation() // Leo
carbon.Now().SetLocale("en").Constellation() // Leo
carbon.Now().SetLocale("zh-CN").Constellation() // 狮子座
// 获取当前季节
carbon.Now().Season() // Summer
carbon.Now().SetLocale("en").Season() // Summer
carbon.Now().SetLocale("zh-CN").Season() // 夏季

carbon还提供了获取星座、季节、农历的方法, 以及设计json编解码的数据格式,数据库日期格式的支持。

总体来说, carbon提供了非常丰富,可以说是保姆级的方法,让你处理日期时间的时候不用再做格外的处理。

robfig/cron

在做业务开发的时候,我们经常会设置一些定时器,有些情况下我们使用Ticker就好了,但是我们想做更细致的定时任务的控制,就得另想办法了,大家第一个想到的就是Linux的cron功能,非常的灵活,所以Go生态圈中也有相应的库,这里给大家介绍两个。

robfig/cron是一款兼容Linux cron格式的定时任务库,同时它还提供了扩展的格式,支持秒粒度的设定,这是一种兼容知名的Java Tigger库quartz的格式。我们以Linux cron格式介绍。

虽然cron提供AddFuncAddJobSchedule,但是大同小异,我们一般用AddFunc去增加定时任务。AddFunc的第一个参数是cron 表达式,第二个参数是无参函数,用来在cron定义的表达式包额诶触发时要执行的函数。
cron使用的例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
c := cron.New()
c.AddFunc("30 * * * *", func() { fmt.Println("在每个小时的30分钟的时候实行") })
c.AddFunc("30 3-6,20-23 * * *", func() { fmt.Println("在每天早上3-6点, 晚上8-11点的30分钟执行") })
c.AddFunc("CRON_TZ=Asia/Shanghai 30 04 * * *", func() { fmt.Println("在每天的北京时间04:30执行") }) // 如果不指定,默认使用机器的时区
c.AddFunc("@hourly", func() { fmt.Println("每一小时执行。从1小时后开始") })
c.AddFunc("@every 1h30m", func() { fmt.Println("每一小时30分执行,1小时30分开始") })
c.Start()
...
// 函数在它们的goroutine中异步的执行
...
// 期间可以安全的增加任务
c.AddFunc("@daily", func() { fmt.Println("每天执行") })
...
// 可以检查任务的状态
inspect(c.Entries())
...
c.Remove(entryID) // 移除某个任务
...
c.Stop() // 不再执行后续的任务

`

cron的格式

1
2
3
4
5
6
7
字段名 | 强制设置? | 允许值 | 允许的特殊字符
---------- | ---------- | -------------- | --------------------------
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 | * / , - ?

维基百科也介绍了cron的格式:

1
2
3
4
5
6
7
8
9
# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12)
# │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday;
# │ │ │ │ │ 7 is also Sunday on some systems)
# │ │ │ │ │
# │ │ │ │ │
# * * * * * <command to execute>

特殊字符代表的意义:

  • *: 代表满足这个字段的每一个值。
  • /: 代表对于时间范围的步数,比如第一个字段*/5代表每5分钟,也就是5,10,15,20,25,30,35,40,45,50,55,00分钟。
  • ,: 代表一组列表,比如第五个字段MON,WED,FRI代表每星期一、星期三、星期五会触发。
  • -: 代表一个范围,比如第二个字段10-15代表会在每天10点、11点、12点、13点、14点、15点被触发。
  • ?: 有时候可以在第三个和第五个字段代替*

同时这个库还提供预定义的几种形式:

1
2
3
4
5
6
7
预定义类型 | 描述 | 等价格式
----- | ----------- | -------------
@yearly (or @annually) | 在每年的一月一日零点 | 0 0 1 1 *
@monthly | 在每月的第一天零点 | 0 0 1 * *
@weekly | 在每周的第一天零点,也就是周日零点 | 0 0 * * 0
@daily (or @midnight) | 每天零点运行 | 0 0 * * *
@hourly | 每小时开始的时候运行 | 0 * * * *

go-co-op/gocron

go-co-op/gocron是另外一个优秀的定时任务库。

它提供了丰富的例子,所以很容易上手,你很容易把它应用到项目中。它的cron解析使用的就是上面的robfig/cron库,你可以使用cron格式实现定时任务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main
import (
"time"
"github.com/go-co-op/gocron"
)
var task = func() {}
func main() {
s := gocron.NewScheduler(time.UTC)
_, _ = s.Cron("*/1 * * * *").Do(task) // 每分钟执行一次
_, _ = s.Cron("0 1 * * *").Do(task) // 每天一点执行
_, _ = s.Cron("0 0 * * 6,0").Do(task) // 周末的零点执行
}

但是它还提供了其它人性化的设置,不一定使用cron配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
s := gocron.NewScheduler(time.UTC)
s.Every(5).Seconds().Do(func(){ ... })
// 每5分钟
s.Every("5m").Do(func(){ ... })
// 每5天
s.Every(5).Days().Do(func(){ ... })
s.Every(1).Month(1, 2, 3).Do(func(){ ... })
// set time
s.Every(1).Day().At("10:30").Do(func(){ ... })
// set multiple times
s.Every(1).Day().At("10:30;08:00").Do(func(){ ... })
s.Every(1).Day().At("10:30").At("08:00").Do(func(){ ... })
// Schedule each last day of the month
s.Every(1).MonthLastDay().Do(func(){ ... })
// Or each last day of every other month
s.Every(2).MonthLastDay().Do(func(){ ... })
// cron expressions supported
s.Cron("*/1 * * * *").Do(task) // every minute
// 异步执行,避免任务会阻塞scheduler
s.StartAsync()