避免重复发明轮子。如果有一些好用的库,我们就直接使用就好了,没必要做一些重复的工作,如果这些库不能满足需求,不妨提交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 35 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 31 / 设置区域 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 20 21 22 23 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的格式**
字段名
强制设置?
允许值
允许的特殊字符
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
* / , - ?
1 2 [维基百科](https://en.wikipedia.org/wiki/Cron)也介绍了cron的格式:
┌───────────── 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) │ │ │ │ │ │ │ │ │ │ * * * * * 1 2 3 4 5 6 7 8 9 10 **特殊字符**代表的意义: - `*`: 代表满足这个字段的每一个值。 - `/`: 代表对于时间范围的步数,比如第一个字段`*/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点被触发。 - `?`: 有时候可以在第三个和第五个字段代替`*` 同时这个库还提供预定义的几种形式:
预定义类型
描述
等价格式
@yearly (or @annually)
在每年的一月一日零点
0 0 1 1 *
@monthly
在每月的第一天零点
0 0 1 * *
@weekly
在每周的第一天零点,也就是周日零点
0 0 * * 0
@daily (or @midnight)
每天零点运行
0 0 * * *
@hourly
每小时开始的时候运行
0 * * * *
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 ## go-co-op/gocron [go-co-op/gocron](https://github.com/go-co-op/gocron)是另外一个优秀的定时任务库。 它提供了丰富的例子,所以很容易上手,你很容易把它应用到项目中。它的cron解析使用的就是上面的robfig/cron库,你可以使用cron格式实现定时任务: ```go 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()