Go 語言標準庫之 time 包
阿新 • • 發佈:2021-12-06
Go 語言的 time 包提供了時間的顯示和測量用的函式。日曆的計算採用的是公曆。
Location 型別
在 Go 語言中,表示時區的型別是type Location struct{...}
,程式碼中使用*time.Location
物件。
// Location 代表一個(關聯到某個時間點的)地點,以及該地點所在的時區 type Location struct { // 內含隱藏或非匯出欄位 } // Local 代表系統本地,對應本地時區 var Local *Location = &localLoc // UTC 代表通用協調時間,對應零時區 var UTC *Location = &utcLoc // 返回使用給定的名字建立的 Location // 如果 name 是 "" 或 "UTC",返回 UTC;如果 name 是 "Local",返回 Local; // 否則 name 應該是 IANA 時區資料庫裡有記錄的地點名,如 "America/New_York" func LoadLocation(name string) (*Location, error) // 自定義地點和時區名稱 name,在零時區基礎上根據時間偏移量 offset(單位秒)建立並返回一個 Location func FixedZone(name string, offset int) *Location // 返回對時區資訊的描述,返回值繫結為 LoadLocation 或 FixedZone 函式建立 l 時的 name 引數 func (l *Location) String() string
示例程式碼如下:
package main import ( "fmt" "time" ) func main() { // 獲取本地當前時間 now := time.Now() // 獲取本地時區 // 方式一 loc1 := time.Local fmt.Println(loc1.String(), now.In(loc1)) // Local 2021-12-04 14:19:42.8397569 +0800 CST // 方式二 loc2, _ := time.LoadLocation("Local") fmt.Println(loc2.String(), now.In(loc2)) // Local 2021-12-04 14:19:42.8397569 +0800 CST // 獲取零時區 // 方式一 utc1 := time.UTC fmt.Println(utc1.String(), now.In(utc1)) // UTC 2021-12-04 06:19:42.8397569 +0000 UTC // 方式二 utc2, _ := time.LoadLocation("") fmt.Println(utc2.String(), now.In(utc2)) // UTC 2021-12-04 06:19:42.8397569 +0000 UTC // 方式三 utc3, _ := time.LoadLocation("UTC") fmt.Println(utc3.String(), now.In(utc3)) // UTC 2021-12-04 06:19:42.8397569 +0000 UTC // 指定地點和時區,東八區 shanghai, _ := time.LoadLocation("Asia/Shanghai") fmt.Println(shanghai.String(), now.In(shanghai)) // Asia/Shanghai 2021-12-04 14:19:42.8397569 +0800 CST // 自定義地點和時區名稱,東八區 beijing := time.FixedZone("Beijing", 8*60*60) fmt.Println(beijing.String(), now.In(beijing)) // Beijing 2021-12-04 14:19:42.8397569 +0800 Beijing }
Time 型別
Time
型別表示時間,我們可以通過以下函式獲取時間物件,然後獲取時間物件的年月日時分秒等資訊。
獲取 Time 物件
// Time 代表一個納秒精度的時間點 type Time struct { // 內含隱藏或非匯出欄位 } // 返回本地當前時間 func Now() Time // 返回一個時區為 loc、當地時間為 year-month-day hour:min:sec + nsec nanoseconds 的時間 func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time // 建立一個本地時間,對應 sec 和 nsec 表示的 Unix 時間(從 January 1, 1970 UTC 至該時間的秒數和納秒數) func Unix(sec int64, nsec int64) Time // 返回採用本地地點和本地時區,但指向同一時間點的 Time func (t Time) Local() Time // 返回採用 UTC 和零時區,但指向同一時間點的 Time func (t Time) UTC() Time // 返回採用 loc 指定的地點和時區,但指向同一時間點的 Time func (t Time) In(loc *Location) Time
示例程式碼如下:
package main
import (
"fmt"
"time"
)
func main() {
// Now:獲取本地當前時間
now := time.Now()
fmt.Println(now) // 2021-12-04 14:43:13.5010095 +0800 CST m=+0.004735101
// Date:自定義時間
setTime1 := time.Date(2021, 12, 4, 12, 3, 56, 0, now.Location())
fmt.Println(setTime1) // 2021-12-04 12:03:56 +0800 CST
// Unix:自定義時間
setTime2 := time.Unix(setTime1.Unix(), 1000).In(now.Location())
fmt.Println(setTime2) // 2021-12-04 12:03:56.000001 +0800 CST
}
獲取 Time 基本資訊
// Weekday 代表一週的某一天
type Weekday int
const (
Sunday Weekday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
// String 返回該日(周幾)的英文名("Sunday"、"Monday",……)
func (d Weekday) String() string
// Month代表一年的某個月
type Month int
const (
January Month = 1 + iota
February
March
April
May
June
July
August
September
October
November
December
)
// String 返回月份的英文名("January","February",……)
func (m Month) String() string
// 返回 t 的地點和時區資訊
func (t Time) Location() *Location
// 計算 t 所在的時區,返回該時區的規範名(如"CET")和該時區相對於 UTC 的時間偏移量(單位秒)
func (t Time) Zone() (name string, offset int)
// 將 t 表示為 Unix 時間,即從時間點 January 1, 1970 UTC 到時間點 t 所經過的時間(單位秒)
func (t Time) Unix() int64
// 將 t 表示為 Unix 時間,即從時間點 January 1, 1970 UTC 到時間點 t 所經過的時間(單位納秒)
func (t Time) UnixNano() int64
// 返回時間點 t 對應的年、月、日
func (t Time) Date() (year int, month Month, day int)
// 返回 t 對應的那一天的時、分、秒
func (t Time) Clock() (hour, min, sec int)
// 返回時間點 t 對應的年份
func (t Time) Year() int
// 返回時間點 t 對應那一年的第幾月
func (t Time) Month() Month
// 返回時間點 t 對應的 ISO 9601 標準下的年份和星期編號,星期編號範圍 [1,53]
// 1 月 1 號到 1 月 3 號可能屬於上一年的最後一週,12 月 29 號到 12 月 31 號可能屬於下一年的第一週
func (t Time) ISOWeek() (year, week int)
// 返回時間點 t 對應的那一年的第幾天,平年的返回值範圍 [1,365],閏年 [1,366]
func (t Time) YearDay() int
// 返回時間點 t 對應那一月的第幾日
func (t Time) Day() int
// 返回時間點 t 對應的那一週的周幾
func (t Time) Weekday() Weekday
// 返回 t 對應的那一天的第幾小時,範圍 [0, 23]
func (t Time) Hour() int
// 返回 t 對應的那一小時的第幾分種,範圍 [0, 59]
func (t Time) Minute() int
// 返回 t 對應的那一分鐘的第幾秒,範圍 [0, 59]
func (t Time) Second() int
// 返回 t 對應的那一秒內的納秒偏移量,範圍 [0, 999999999]
func (t Time) Nanosecond() int
示例程式碼如下:
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
// 地點和時區資訊
fmt.Println(now.Location()) // Local
// 時區
fmt.Println(now.Zone()) // CST 28800
// Unix 時間(單位秒)
fmt.Println(now.Unix()) // 1638602223
// Unix 時間(單位納秒)
fmt.Println(now.UnixNano()) // 1638602223393227200
fmt.Println(now.Date()) // 2021 December 4
fmt.Println(now.Clock()) // 15 17 3
fmt.Println(now.Year()) // 2021
fmt.Println(now.Month()) // December
fmt.Println(now.ISOWeek()) // 2021 48
fmt.Println(now.YearDay()) // 338
fmt.Println(now.Day()) // 4
fmt.Println(now.Weekday()) // Saturday
fmt.Println(now.Hour()) // 15
fmt.Println(now.Minute()) // 17
fmt.Println(now.Second()) // 3
fmt.Println(now.Nanosecond()) // 393227200
}
Time 比較和運算
// Duration 型別代表兩個時間點之間經過的時間,以納秒為單位。可表示的最長時間段大約 290 年
type Duration int64
const (
Nanosecond Duration = 1
Microsecond = 1000 * Nanosecond
Millisecond = 1000 * Microsecond
Second = 1000 * Millisecond
Minute = 60 * Second
Hour = 60 * Minute
)
// ParseDuration 解析一個時間段字串
// 一個時間段字串是一個序列,每個片段包含可選的正負號、十進位制數、可選的小數部分和單位字尾,如"300ms"、"-1.5h"、"2h45m"。
// 合法的單位有"ns"、"us" /"µs"、"ms"、"s"、"m"、"h"
func ParseDuration(s string) (Duration, error)
// Hours 將時間段表示為 float64 型別的小時數
func (d Duration) Hours() float64
// Hours 將時間段表示為 float64 型別的分鐘數
func (d Duration) Minutes() float64
// Hours 將時間段表示為 float64 型別的秒數
func (d Duration) Seconds() float64
// Hours 將時間段表示為 int64 型別的納秒數,等價於 int64(d)
func (d Duration) Nanoseconds() int64
// 返回時間段採用 "72h3m0.5s" 格式的字串表示
func (d Duration) String() string
// 表示按照 m 給定的單位,返回四捨五入
func (d Duration) Round(m Duration) Duration
// 表示按照 m 給定的單位,返回舍尾數計算
func (d Duration) Truncate(m Duration) Duration
// 報告 t 是否代表 Time 零值的時間點,January 1, year 1, 00:00:00 UTC
func (t Time) IsZero() bool
// 判斷兩個時間是否相同,會考慮時區的影響,因此不同時區標準的時間也可以正確比較
// 本方法和用 t == u 不同,這種方法還會比較地點和時區資訊
func (t Time) Equal(u Time) bool
// 如果 t 代表的時間點在 u 之前,返回真;否則返回假
func (t Time) Before(u Time) bool
// 如果 t 代表的時間點在 u 之後,返回真;否則返回假
func (t Time) After(u Time) bool
// 返回時間點 t + d
func (t Time) Add(d Duration) Time
// 返回增加了給出的年份、月份和天數的時間點 Time。例如,時間點 January 1, 2011 呼叫 AddDate(-1, 2, 3) 會返回 March 4, 2010
// AddDate 會將結果規範化,類似 Date 函式的做法。因此,舉個例子,給時間點 October 31 新增一個月,會生成時間點 December 1
func (t Time) AddDate(years int, months int, days int) Time
// 返回一個時間段 t - u。如果結果超出了 Duration 可以表示的最大值/最小值,將返回最大值/最小值
// 要獲取時間點 t-d(d 為 Duration),可以使用 t.Add(-d)
func (t Time) Sub(u Time) Duration
// 返回從 t 到現在經過的時間,等價於 time.Now().Sub(t)
func Since(t Time) Duration
// 返回從現在到 t 經過的時間,等價於 t.Sub(time.Now())
func Until(t Time) Duration
示例程式碼如下:
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
time1 := time.Date(2021, 12, 4, 12, 3, 56, 0, now.Location())
fmt.Println(now.IsZero()) // false
fmt.Println(now.Equal(time1)) // false
fmt.Println(now.Before(time1)) // false
fmt.Println(now.After(time1)) // true
fmt.Println(now.Add(time.Hour)) // 2021-12-04 17:16:41.2619698 +0800 CST m=+3600.006734601
fmt.Println(now.Add(-time.Hour)) // 2021-12-04 15:16:41.2619698 +0800 CST m=-3599.993265399
fmt.Println(now.AddDate(1, 1, 1)) // 2023-01-05 16:16:41.2619698 +0800 CST
fmt.Println(now.AddDate(-1, -1, -1)) // 2020-11-03 16:16:41.2619698 +0800 CST
fmt.Println(now.Sub(time1)) // 4h12m45.2619698s
fmt.Println(time.Since(time1)) // 4h12m45.2843379s
fmt.Println(time.Until(time1)) // -4h12m45.2843379s
}
Time 與 string 轉換
// 解析並返回一個格式化的時間字串所代表的時間,缺少時區資訊時,Parse 會將時區設定為 UTC 時間
// layout 定義了參考時間:Mon Jan 2 15:04:05 -0700 MST 2006
func Parse(layout, value string) (Time, error)
// ParseInLocation 類似 Parse 但有兩個重要的不同之處
// 第一,當缺少時區資訊時,Parse 將時區設定為 UTC 時間,而 ParseInLocation 將返回值的 Location 設定為loc;
// 第二,當時間字串提供了時區偏移量資訊時,Parse 會嘗試去匹配本地時區,而 ParseInLocation 會去匹配loc
func ParseInLocation(layout, value string, loc *Location) (Time, error)
// 將時間字串轉換為 time.Duration 物件
// 字串可以使用這些單位: "ns", "us" (or "µs"), "ms", "s", "m", "h"
func ParseDuration(s string) (Duration, error)
// 根據 layout 指定的格式返回 t 代表的時間點的格式化文字表示
// layout 定義了參考時間:Mon Jan 2 15:04:05 -0700 MST 2006
func (t Time) Format(layout string) string
// 該方法為基本格式化時間物件為字串,用該方法可以避免申請臨時物件
// 需要由使用者控制傳入 buf 的長度,如果長度不夠,則返回值是擴容後的可用的資料,而 buf 裡面資料無效
func (t Time) AppendFormat(b []byte, layout string) []byte
// 返回採用如下格式字串的格式化時間:"2006-01-02 15:04:05.999999999 -0700 MST"
func (t Time) String() string
示例程式碼如下:
package main
import (
"fmt"
"time"
)
func main() {
// Go 語言格式化時間模板不是常見的 Y-m-d H:M:S,而是一個固定時間,記憶口訣為 2006 1 2 3 4
const timeFormat = "2006-01-02 15:04:05"
now := time.Now()
// 將字串轉換換為時間
fmt.Println(time.Parse(timeFormat, "2021-12-04 10:53:58")) // 2021-12-04 10:53:58 +0000 UTC <nil>
fmt.Println(time.ParseInLocation(timeFormat, "2021-12-04 10:53:58", now.Location())) // 2021-12-04 10:53:58 +0800 CST <nil>
fmt.Println(time.ParseDuration("24h5m6s123ms456us321ns")) // 24h5m6.123456321s <nil>
// 將當前時間轉換為字串
fmt.Println(now.Format(timeFormat)) // 2021-12-04 16:56:38
buf := make([]byte, 0, 64)
fmt.Printf("%s\n", now.AppendFormat(buf, timeFormat)) // 2021-12-04 16:56:38
}
Timer 型別
// Timer 型別代表單次時間事件
// 當 Timer 到期時,當時的時間會被髮送給 C,除非 Timer 是被 AfterFunc 函式建立的。
type Timer struct {
C <-chan Time
// 內含隱藏或非匯出欄位
}
// 建立一個 Timer,它會在最少過去時間段 d 後到期,向其自身的 C 欄位傳送當時的時間
func NewTimer(d Duration) *Timer
// 另起一個 Go 協程等待時間段 d 過去,然後呼叫 f
// 它返回一個 Timer,可以通過呼叫其 Stop 方法來取消等待和對 f 的呼叫。
func AfterFunc(d Duration, f func()) *Timer
// 使 t 重新開始計時,(本方法返回後再)等待時間段 d 過去後到期
// 如果呼叫時 t 還在等待中會返回真;如果 t 已經到期或者被停止了會返回假
func (t *Timer) Reset(d Duration) bool
// 停止 Timer 的執行。如果停止了 t 會返回真;如果 t 已經被停止或者過期了會返回假
// Stop 不會關閉通道 t.C,以避免從該通道的讀取不正確的成功
func (t *Timer) Stop() bool
// Sleep 阻塞當前 Go 協程至少 d 代表的時間段。d <= 0 時,Sleep 會立刻返回
func Sleep(d Duration)
示例程式碼如下:
package main
import (
"fmt"
"time"
)
func main() {
// 大多數場景用下面這種方式處理超時邏輯
// FIXME: 特別注意下面方法存在記憶體洩露,當大量呼叫chanTimeout
// FIXME: 會產生大量time.After,此時如果都在超時時間內走handle
// FIXME: 那麼time.After產生的物件都佔著記憶體,直到超過timeout才會GC釋放
chanTimeout := func(c <-chan int, timeout time.Duration) {
select {
case tmp, ok := <-c:
// handle(tmp, ok)
fmt.Println(tmp, ok)
case <-time.After(timeout):
fmt.Println("timeout")
}
}
// FIXME: 使用下面方法更安全,當在超時時間內走到處理流程,手動釋放記憶體
chanTimeout = func(c <-chan int, timeout time.Duration) {
t := time.NewTimer(timeout)
select {
case tmp, ok := <-c:
t.Stop() // 當走正常邏輯時手動停掉timer
// handle(t, ok)
fmt.Println(tmp, ok)
case <-t.C:
fmt.Println("timeout")
}
}
send := make(chan int)
go chanTimeout(send, time.Second)
time.Sleep(time.Millisecond * 800)
select {
case send <- 100: // 在timeout之前進入處理邏輯
default:
}
go chanTimeout(send, time.Second)
time.Sleep(time.Second * 2)
select { // 可以嘗試不用select + default,只簡單的使用send <- 200會不會報錯
case send <- 200: // 直接進入timeout邏輯
default:
}
fmt.Println(time.Now().String())
timer := time.AfterFunc(time.Second, func() {
fmt.Println(time.Now().String())
})
time.Sleep(time.Second * 2)
timer.Reset(time.Second * 5) // 重置一下,5秒後會再列印一條
time.Sleep(time.Second * 6)
select {
case <-timer.C:
default:
}
}
Ticker 型別
// Ticker 保管一個通道,並每隔一段時間向其傳遞 "tick"
type Ticker struct {
C <-chan Time // 週期性傳遞時間資訊的通道
// 內含隱藏或非匯出欄位
}
// 返回一個新的 Ticker,該 Ticker 包含一個通道欄位,並會每隔時間段 d 就向該通道傳送當時的時間
// 它會調整時間間隔或者丟棄 tick 資訊以適應反應慢的接收者。如果 d<=0 會 panic。關閉該 Ticker 可以釋放相關資源
func NewTicker(d Duration) *Ticker
// 關閉一個 Ticker。在關閉後,將不會發送更多的 tick 資訊
// Stop 不會關閉通道 t.C,以避免從該通道的讀取不正確的成功
func (t *Ticker) Stop()
// 會在另一執行緒經過時間段 d 後向返回值傳送當時的時間。等價於 NewTimer(d).C
func After(d Duration) <-chan Time
// Tick 是 NewTicker 的封裝,只提供對 Ticker 的通道的訪問。如果不需要關閉 Ticker,本函式就很方便。
func Tick(d Duration) <-chan Time
示例程式碼如下:
package main
import (
"fmt"
"time"
)
func main() {
i := 0
// 按照如下方式定時獲取 time 非常簡潔
for t := range time.Tick(time.Second) {
i++
if i > 3 {
break
}
fmt.Println(t.String())
}
// 如下測試是常規用法
t := time.NewTicker(time.Second)
send := make(chan int)
go func(c chan<- int) {
i := 0
for {
time.Sleep(time.Millisecond * 600)
c <- i
i++
}
}(send)
go func(c <-chan int) {
for {
select {
case tmp, ok := <-t.C:
fmt.Println(tmp.String(), ok)
case tmp, ok := <-c:
fmt.Println(tmp, ok)
}
}
}(send)
time.Sleep(time.Second * 10)
t.Reset(time.Second)
t.Stop()
}