避免重复发明轮子。如果有一些好用的库,我们就直接使用就好了,没必要做一些重复的工作,如果这些库不能满足需求,不妨提交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() now.BeginningOfMinute() now.BeginningOfHour() now.BeginningOfDay() now.BeginningOfWeek() now.BeginningOfMonth() now.BeginningOfQuarter() now.BeginningOfYear()
|
或者这个时刻的此分钟最后的时刻、此小时最后的时刻、此天的最后的时刻、此周最后的时刻、此月最后的时刻、此季节最后的时刻、此半年最后的时刻,此年最后的时刻:
1 2 3 4 5 6 7 8 9 10
| now.EndOfMinute() now.EndOfHour() now.EndOfDay() now.EndOfWeek() now.EndOfMonth() now.EndOfQuarter() now.EndOfYear() now.WeekStartDay = time.Monday now.EndOfWeek()
|
如果你是求当前时刻的开始时刻和结束时刻,你可以使用包的函数,它提供了便利的函数,比如当前小时的开始时刻:
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()) myConfig.With(t).BeginningOfWeek()
|
日期解析
标注库的日期解析方式是以样本的方式提供的,比如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
| t, err := now.Parse("2017") t, err := now.Parse("2017-10") t, err := now.Parse("2017-10-13") t, err := now.Parse("1999-12-12 12") t, err := now.Parse("1999-12-12 12:20") t, err := now.Parse("1999-12-12 12:20:21") t, err := now.Parse("10-13") t, err := now.Parse("12:20") t, err := now.Parse("12:20:13") t, err := now.Parse("14") t, err := now.Parse("99:99") now.MustParse("2013-01-13") now.MustParse("02-17") now.MustParse("2-17") now.MustParse("8") now.MustParse("2002-10-12 22:14") now.MustParse("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() carbon.CreateFromTimestampMilli(1649735755981).ToString() carbon.CreateFromDate(2020, 8, 5).ToString() carbon.CreateFromTime(13, 14, 15).ToString()
|
和标准库互转:
1 2 3 4
| carbon.Time2Carbon(time.Now()) 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()) carbon.Now().ToString() carbon.Now().ToDateTimeString() carbon.Now().ToDateString() carbon.Now().ToTimeString() fmt.Sprintf("%s", carbon.Yesterday()) carbon.Yesterday().ToString() fmt.Sprintf("%s", carbon.Tomorrow()) carbon.Tomorrow().ToString() carbon.Tomorrow().ToDateTimeString()
|
一些字符串格式例子:
1 2 3 4 5 6 7 8
| carbon.Parse("2020-08-05T13:14:15.999999999+08:00").ToDateTimeString() carbon.Parse("2020-08-05T13:14:15.999999999+08:00").ToDateTimeMilliString() carbon.Parse("2020-08-05T13:14:15.999999999+08:00").ToDateTimeMicroString() carbon.Parse("2020-08-05T13:14:15.999999999+08:00").ToDateTimeNanoString()
|
解析
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() carbon.Parse("yesterday").ToString() carbon.Parse("tomorrow").ToString() carbon.Parse("2020").ToString() carbon.Parse("2020-8").ToString() carbon.Parse("2020-08").ToString() carbon.Parse("2020-8-5").ToString() carbon.Parse("2020-8-05").ToString() carbon.Parse("2020-08-05").ToString() carbon.Parse("2020-08-05.999").ToString() carbon.Parse("2020-08-05.999999").ToString() carbon.Parse("2020-08-05.999999999").ToString() carbon.Parse("2020-8-5 13:14:15").ToString() carbon.Parse("2020-8-05 13:14:15").ToString() carbon.Parse("2020-08-05T13:14:15.999999999+08:00").ToString() carbon.Parse("20200805").ToString() carbon.Parse("20200805131415.999999999+08:00").ToString()
|
模板格式:
1 2 3 4
| carbon.ParseByFormat("2020|08|05 13|14|15", "Y|m|d H|i|s").ToDateTimeString() carbon.ParseByFormat("It is 2020-08-05 13:14:15", "\\I\\t \\i\\s Y-m-d H:i:s").ToDateTimeString() carbon.ParseByFormat("今天是 2020年08月05日13时14分15秒", "今天是 Y年m月d日H时i分s秒").ToDateTimeString() carbon.ParseByFormat("2020-08-05 13:14:15", "Y-m-d H:i:s", carbon.Tokyo).ToDateTimeString()
|
或者Go标准库布局模式:
1 2 3 4
| carbon.ParseByLayout("2020|08|05 13|14|15", "2006|01|02 15|04|05").ToDateTimeString() carbon.ParseByLayout("It is 2020-08-05 13:14:15", "It is 2006-01-02 15:04:05").ToDateTimeString() carbon.ParseByLayout("今天是 2020年08月05日13时14分15秒", "今天是 2006年01月02日15时04分05秒").ToDateTimeString() carbon.ParseByLayout("2020-08-05 13:14:15", "2006-01-02 15:04:05", carbon.Tokyo).ToDateTimeString()
|
开始时刻和结束时刻
和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() carbon.Parse("2020-08-05 13:14:15").EndOfCentury().ToDateTimeString() carbon.Parse("2020-08-05 13:14:15").StartOfDecade().ToDateTimeString() carbon.Parse("2021-08-05 13:14:15").StartOfDecade().ToDateTimeString() carbon.Parse("2029-08-05 13:14:15").StartOfDecade().ToDateTimeString() carbon.Parse("2020-08-05 13:14:15").EndOfDecade().ToDateTimeString() carbon.Parse("2021-08-05 13:14:15").EndOfDecade().ToDateTimeString() carbon.Parse("2029-08-05 13:14:15").EndOfDecade().ToDateTimeString() carbon.Parse("2020-08-05 13:14:15").StartOfYear().ToDateTimeString() carbon.Parse("2020-08-05 13:14:15").EndOfYear().ToDateTimeString() carbon.Parse("2020-08-05 13:14:15").StartOfQuarter().ToDateTimeString() carbon.Parse("2020-08-05 13:14:15").EndOfQuarter().ToDateTimeString() ...... carbon.Parse("2020-08-05 13:14:15").StartOfMinute().ToDateTimeString() carbon.Parse("2020-08-05 13:14:15").EndOfMinute().ToDateTimeString() carbon.Parse("2020-08-05 13:14:15").StartOfSecond().ToString() carbon.Parse("2020-08-05 13:14:15").EndOfSecond().ToString()
|
好吧,向上它已经提供劳务世纪初和世纪末的时间点、已经秒级别的开始时间点和结束时间点。
时间旅行
提供了时间时移的功能,增加时间或者减少时间到另外一个时间点。
依然很大气哦,可以提供世纪级别的方法纳秒级别的方法。
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() carbon.Parse("2020-02-29 13:14:15").AddCenturiesNoOverflow(3).ToDateTimeString() ...... carbon.Parse("2020-02-29 13:14:15").AddCenturies(3).ToDateTimeString() carbon.Parse("2020-02-29 13:14:15").AddCenturiesNoOverflow(3).ToDateTimeString() ...... carbon.Parse("2020-08-05 13:14:15.222222222").AddNanoseconds(3).ToString() carbon.Parse("2020-08-05 13:14:15.222222222").AddNanossecond().ToString() carbon.Parse("2020-08-05 13:14:15.222222222").SubNanosseconds(3).ToString() carbon.Parse("2020-08-05 13:14:15.222222222").SubNanossecond().ToString()
|
仁者见仁智者见智,有些人喜欢这种细粒度的方法,挺好。不过看到这么多的方法,提供类似的功能,我会提供一个方法,然后第二个单位用枚举类型来指定,这样一个时移通过一个方法就搞定了,这样学习起来和维护起来比较方便,比如我会设计成这样:
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")) carbon.Parse("2021-08-05 13:14:15").DiffAbsInYears(carbon.Parse("2020-08-05 13:14:15")) ... / 相差多少秒 carbon.Parse("2020-08-05 13:14:15").DiffInSeconds(carbon.Parse("2020-08-05 13:14:14")) carbon.Parse("2020-08-05 13:14:15").DiffAbsInSeconds(carbon.Parse("2020-08-05 13:14:14")) carbon.Now().DiffInString() carbon.Now().AddYearsNoOverflow(1).DiffInString() carbon.Now().SubYearsNoOverflow(1).DiffInString() carbon.Now().DiffAbsInString(carbon.Now()) carbon.Now().AddYearsNoOverflow(1).DiffAbsInString(carbon.Now()) carbon.Now().SubYearsNoOverflow(1).DiffAbsInString(carbon.Now())
|
一些时间判断
比如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| carbon.Parse("0").IsZero() carbon.Parse("0000-00-00 00:00:00").IsZero() carbon.Parse("0").IsZero() carbon.Parse("0000-00-00 00:00:00").IsZero() carbon.Parse("2020-08-05 13:14:15").IsLeapYear() carbon.Parse("2020-08-05 13:14:15").IsLongYear() carbon.Parse("2020-08-05 13:14:15").IsJanuary() carbon.Parse("2020-08-05 13:14:15").IsLeapYear() carbon.Parse("2020-08-05 13:14:15").IsLongYear() carbon.Parse("2020-08-05 13:14:15").IsJanuary()
|
设置时间的某个单位
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() carbon.Parse("2020-07-05 13:14:15").SetLocale("zh-CN").DiffForHumans() carbon.Parse("2020-01-01").SetDateTime(2019, 2, 2, 13, 14, 15).ToString() carbon.Parse("2020-01-01").SetDateTime(2019, 2, 31, 13, 14, 15).ToString() carbon.Parse("2020-01-01").SetDateTimeMilli(2019, 2, 2, 13, 14, 15, 999).ToString() carbon.Parse("2020-01-01").SetDateTimeMilli(2019, 2, 31, 13, 14, 15, 999).ToString() carbon.Parse("2020-01-01").SetDateTimeMicro(2019, 2, 2, 13, 14, 15, 999999).ToString() carbon.Parse("2020-01-01").SetDateTimeMicro(2019, 2, 31, 13, 14, 15, 999999).ToString() carbon.Parse("2020-01-01").SetDateTimeNano(2019, 2, 2, 13, 14, 15, 999999999).ToString() carbon.Parse("2020-01-01").SetDateTimeNano(2019, 2, 31, 13, 14, 15, 999999999).ToString()
|
获取时间的某个单位
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() carbon.Parse("2020-07-05 13:14:15").SetLocale("zh-CN").DiffForHumans() carbon.Parse("2020-01-01").SetDateTime(2019, 2, 2, 13, 14, 15).ToString() carbon.Parse("2020-01-01").SetDateTime(2019, 2, 31, 13, 14, 15).ToString() carbon.Parse("2020-01-01").SetDateTimeMilli(2019, 2, 2, 13, 14, 15, 999).ToString() carbon.Parse("2020-01-01").SetDateTimeMilli(2019, 2, 31, 13, 14, 15, 999).ToString() carbon.Parse("2020-01-01").SetDateTimeMicro(2019, 2, 2, 13, 14, 15, 999999).ToString() carbon.Parse("2020-01-01").SetDateTimeMicro(2019, 2, 31, 13, 14, 15, 999999).ToString() carbon.Parse("2020-01-01").SetDateTimeNano(2019, 2, 2, 13, 14, 15, 999999999).ToString() carbon.Parse("2020-01-01").SetDateTimeNano(2019, 2, 31, 13, 14, 15, 999999999).ToString() carbon.Now().Locale() carbon.Now().SetLocale("zh-CN").Locale() carbon.Now().Constellation() carbon.Now().SetLocale("en").Constellation() carbon.Now().SetLocale("zh-CN").Constellation() carbon.Now().Season() carbon.Now().SetLocale("en").Season() 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提供AddFunc
、AddJob
、Schedule
,但是大同小异,我们一般用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() ... ... 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(){ ... }) s.Every("5m").Do(func(){ ... }) s.Every(5).Days().Do(func(){ ... }) s.Every(1).Month(1, 2, 3).Do(func(){ ... }) s.Every(1).Day().At("10:30").Do(func(){ ... }) s.Every(1).Day().At("10:30;08:00").Do(func(){ ... }) s.Every(1).Day().At("10:30").At("08:00").Do(func(){ ... }) s.Every(1).MonthLastDay().Do(func(){ ... }) s.Every(2).MonthLastDay().Do(func(){ ... }) s.Cron("*/1 * * * *").Do(task) s.StartAsync()
|