Go: 一些關於時間和定時任務的庫

避免重複發明輪子。如果有一些好用的庫,我們就直接使用就好了,沒必要做一些重複的工作,如果這些庫不能滿足需求,不妨提交 pull request 或者 clone 它們,提升它們,優化它們,當前前提是你得知道它們。

這篇文章給大家介紹一些關於時間和類似 linux cron 功能的定時任務庫。

jinzhu/now

張金柱大佬除了給大家貢獻了 gorm 外,還寫了一些好用的 Go 庫, jinzhu/now[1] 就是之一。

當前,Go 標準庫提供了關於時間的庫 time[2],可以滿足我們 80% 的場景,但是對於一些特殊的場景,使用標準庫卻很麻煩,其它一些編程語言也是這樣,標準庫中的時間相關的函數在一些特殊場景下不方便使用,金柱提供的 now 庫滿足了一些特殊場景的需求,使用起來特別的方便。

這個庫最重要的函數我把它分成兩類:

當然它還包含求四季,以及明確求週日週一的函數 (有的國家和地區星期天算一週的開始、有的週一算一週的開始),這更少用了,我們就不介紹了。

計算開始和最後時刻的函數

給定一個時間,你可以得到這個時刻的此分鐘開始的時刻、此小時開始的時刻、此天的開始的時刻、此周開始的時刻、此月開始的時刻、此季節開始的時刻、此半年開始的時刻,此年開始的時刻:

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

或者這個時刻的此分鐘最後的時刻、此小時最後的時刻、此天的最後的時刻、此周最後的時刻、此月最後的時刻、此季節最後的時刻、此半年最後的時刻,此年最後的時刻:

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

如果你是求當前時刻的開始時刻和結束時刻,你可以使用包的函數,它提供了便利的函數,比如當前小時的開始時刻:

_ = time.BeginningOfHour()

它實際的實現是:

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

你還可以設置特定的時區、日期格式和每週的開始的第一天是星期一還是星期天:

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庫提供了一種容錯式的解析方式,它會遍歷標準庫的時間格式 [3],嘗試使用其中的一種格式進行解析。它的處理方式有遍歷和正則表達式,所以如果是在追求性能和日期格式比較明確的情況下,用標準庫的解析就好,但是如果不追求極致的性能,這個解析能很好的容錯,你不需要記住格式模板,輸入一個日期字符串它就能解析。(哦覺得它還可以做一個優化,除了標準庫提供的日期格式外,允許用戶提供特定的日期格式模板,並且設定優先級)

// 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[4]golang-module/carbon 是一個輕量級、語義化、對開發者友好的 golang 時間處理庫,支持鏈式調用,由夠過癮 [5] 開發。

它提供了非常豐富的函數,看的我老眼昏花。它提供的函數大致分爲幾類。

創建 carbon 實例

根據參數的不同,創建的方式很多,下面只是列出了幾種創建的方法:

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

和標準庫互轉:

// 將 time.Time 轉換成 Carbon
carbon.Time2Carbon(time.Now())
// 將 Carbon 轉換成 time.Time
carbon.Now().Carbon2Time()

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

// 今天此刻
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

一些字符串格式例子:

// 輸出日期時間字符串
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 解析採用標準格式或者模板格式,標準格式如下:

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

模板格式:

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 標準庫佈局模式:

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 的功能類似,求一個時刻開始點和結束點:

// 本世紀開始時間
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

好吧,向上它已經提供勞務世紀初和世紀末的時間點、已經秒級別的開始時間點和結束時間點。

時間旅行

提供了時間時移的功能,增加時間或者減少時間到另外一個時間點。

依然很大氣哦,可以提供世紀級別的方法納秒級別的方法。

// 三個世紀後
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

仁者見仁智者見智,有些人喜歡這種細粒度的方法,挺好。不過看到這麼多的方法,提供類似的功能,我會提供一個方法,然後第二個單位用枚舉類型來指定,這樣一個時移通過一個方法就搞定了,這樣學習起來和維護起來比較方便,比如我會設計成這樣:

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

你喜歡哪種方法,歡迎評論區寫出你的想法。

時間差

求兩個時間的差值, 比如:

// 相差多少年
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

一些時間判斷

比如

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

設置時間的某個單位

/ 設置區域
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.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[6] 就好了,但是我們想做更細緻的定時任務的控制,就得另想辦法了,大家第一個想到的就是 Linux 的 cron 功能,非常的靈活,所以 Go 生態圈中也有相應的庫,這裏給大家介紹兩個。

robfig/cron[7] 是一款兼容 Linux cron 格式的定時任務庫,同時它還提供了擴展的格式,支持秒粒度的設定,這是一種兼容知名的 Java Tigger 庫 quartz[8] 的格式。我們以 Linux cron 格式介紹。

雖然 cron 提供AddFuncAddJobSchedule, 但是大同小異,我們一般用AddFunc去增加定時任務。AddFunc的第一個參數是 cron 表達式,第二個參數是無參函數,用來在 cron 定義的表達式包額誒觸發時要執行的函數。 cron 使用的例子如下:

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 的格式

字段名        | 強制設置?   | 允許值           | 允許的特殊字符
----------   | ---------- | --------------  | --------------------------
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  | * / , - ?

維基百科 [9] 也介紹了 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)
# │ │ │ │ │
# │ │ │ │ │
# * * * * * <command to execute>

特殊字符代表的意義:

同時這個庫還提供預定義的幾種形式:

預定義類型               | 描述                                       | 等價格式
-----                  | -----------                                | -------------
@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[10] 是另外一個優秀的定時任務庫。

它提供了豐富的例子,所以很容易上手,你很容易把它應用到項目中。它的 cron 解析使用的就是上面的 robfig/cron 庫, 你可以使用 cron 格式實現定時任務:

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 配置:

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()

參考資料

[1]

jinzhu/now: https://github.com/jinzhu/now

[2]

time: https://pkg.go.dev/time

[3]

時間格式: https://pkg.go.dev/time#pkg-constants

[4]

golang-module/carbon: https://github.com/golang-module/carbon

[5]

夠過癮: https://www.gouguoyin.com/

[6]

Ticker: https://pkg.go.dev/time#Ticker

[7]

robfig/cron: https://pkg.go.dev/github.com/robfig/cron/v3

[8]

quartz: http://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/tutorial-lesson-06.html

[9]

維基百科: https://en.wikipedia.org/wiki/Cron

[10]

go-co-op/gocron: https://github.com/go-co-op/gocron

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/EAk_LdlfYGwaiDDj9CgqHA