文档翻译自:
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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。