文档翻译自:
Package time

Go 中的 time 包提供了时间展示和计算相关的功能。

使用无闰秒的格列高利历计算时间。

单调时钟(Monotonic Clocks)

(我的理解:描述系统中「逝去」的时间。这个概念是和进程绑定的,一般来说,用来表示当前进程自启动之后「逝去」的时间。)

操作系统通常会提供一个「墙上时钟」读数用来协调和日历时刻同步,同时还有一个与日历时间不同步的「单调时钟」。通常的规则是,「墙上时钟」用与表达时刻读数的,而「单调时钟」是用来度量时间(长度)的。Go 中的 time.Now 直接就返回了包含两种规则的时间,而不是把他们分成两个接口。对于返回的时间类型,时刻相关的计算使用「墙上时钟」读数,而对于度量时间(长度)的操作,比如比较和求差,则是通过「单调时钟」读数来计算的。

下面的代码用于计算 20ms 的时间流逝,他总是会得到一个正值(哪怕你在执行过程中调整了系统时钟)。

start := time.Now()
... 花费 20 ms ...
t := time.Now()
elapsed := t.Sub(start) // 永远是正值。
// 类似 time.Since(start), time.Until(deadline), time.Now().Before(deadline) 也对系统时间的修改免疫。

下面讲一下「单调时钟」的一些细节,但是不了解这些也不会影响你使用 time 包。

time.Now 返回的 Time 类型包含了一个「单调时钟」读数。如果 Time t 有「单调时钟」读数,那么方法 t.Add 就会在「墙上时钟」和「单调时钟」读数上面都加上对应的值。计算时涉及到「墙上时钟」相关的方法例如 t.AddDate(y, m, d), t.Round(d), t.Truncate(d) 会直接丢弃掉「单调时钟」信息。t.In, t.Local, t.UTC 这一类解释「墙上时钟」的方法同样也会丢弃「单调时钟」信息。如果要手工丢弃 Time 类型中的「单调时钟」信息,最简洁的方法是 t = t.Round(0)

如果时间类型 t 和 u 都包含「单调时钟」读数,那么 t.After(u) t.Before(u) t.Equal(u)t.Sub(u) 都将使用「单调时钟」读数进行操作。如果 t 或 u 任意一个不包含「单调时钟」读数,那么上述操作就会==退回到使用「墙上时钟」进行计算==。

某些系统在休眠的时候「单调时钟」也会暂停。在这些系统中,t.Sub(u) 就无法精确的反映出 t 和 u 之间流逝的真正时长了。

因为「单调时钟」总是只对当前运行的进程有意义,所以序列化操作类似 t.GobEncode, t.MarshalBinary, t.MarshalJSON, 和 t.MarshalText 都会丢弃「单调时钟」读数,同时 t.Format 也无法提供对其的格式化输出。类似的,通过一系列的构建方法 time.Date, time.Parse, time.ParseInLocation, time.Unix 以及反序列化构建方法 t.GobDecode, t.UnmarshalBinary, t.UnmarshalJSON, t.UnmarshalText 创建的时间类型,都不会包含「单调时钟」读数。

值得注意的是,(对于时间类型)== 操作符不仅仅对时间做了比较,而且还对时区以及「单调时钟」读数也做了考虑。请参考 Time 类型中关于时间比较的相关描述。

为了方便调试,t.String在「单调时钟」存在的情况下返回了读数。如果 t != u 是因为「单调时钟」不一致,那么 t.String()u.String() 结果也会呈现不一致。

注意点:

  • Time 类型应当使用值而不是指针(不要用 *time.Time)。
  • Time type 大多是时候是并发安全的,除开 GobDecode, UnmarshalBinary, UnmarshalJSON, UnmarshalText 这些方法。(注意到这些方法都是和「单调时钟」有关的)。
  • 因为 == 操作涉及到时区和「单调时钟」(看起来相似的两个 Time 类型的 == 操作可能为 false),因此不要将 Time type 作为 map 的 key。当你想确认两个时刻是否一致时候,可以使用方法 t.Equal

测试程序:

package main

import (
    "fmt"
    "time"
)

func main() {
    secondsEastOfUTC := int((8 * time.Hour).Seconds())
    beijing := time.FixedZone("Beijing Time", secondsEastOfUTC)

    // Unlike the equal operator, Equal is aware that d1 and d2 are the
    // same instant but in different time zones.
    d1 := time.Date(2000, 2, 1, 12, 30, 0, 0, time.UTC)
    d2 := time.Date(2000, 2, 1, 20, 30, 0, 0, beijing)

    datesEqualUsingEqualOperator := d1 == d2
    datesEqualUsingFunction := d1.Equal(d2)

    fmt.Printf("datesEqualUsingEqualOperator = %v\n", datesEqualUsingEqualOperator)
    fmt.Printf("datesEqualUsingFunction = %v\n", datesEqualUsingFunction)

    now := time.Now()
    nowConstructed := time.Date(now.Year(), now.Month(),
        now.Day(), now.Hour(), now.Minute(), now.Second(),
        now.Nanosecond(), now.Location())

    fmt.Println("---")
    fmt.Printf("now == nowConstructed: %v\n", now == nowConstructed)
    fmt.Printf("now Equal nowConstructed = %v\n", now.Equal(nowConstructed))

    fmt.Println("---")
    fmt.Printf("now.String() %s\n", now)
    fmt.Printf("now.String().AddData %s\n", now.AddDate(0, 0, 0))   // monotonic clock disposed.
    fmt.Printf("now.String().Add %s\n", now.Add(time.Nanosecond*1)) // monotonic still available.

    fmt.Printf("now.UTC() %s\n", now.UTC())
}

执行结果

datesEqualUsingEqualOperator = false
datesEqualUsingFunction = true
---
now == nowConstructed: false
now Equal nowConstructed = true
---
now.String() 2021-04-17 16:39:21.256967 +0800 CST m=+0.000207251
now.String().AddData 2021-04-17 16:39:21.256967 +0800 CST
now.String().Add 2021-04-17 16:39:21.256967001 +0800 CST m=+0.000207252
now.UTC() 2021-04-17 08:39:21.256967 +0000 UTC

秦川
6 声望3 粉丝

September 3rd 2019.