Go 标准库学习:time
十二月其三,学习 Go 的 time 包(Go 版本 1.16.5)。
我在阅读 time 包的 API 时,发现注释非常有价值,因此从这篇文章开始,如果有包注释的话,会首先翻译一遍。
首先来看 time 包的总注释。注释太长,提炼一下重点:
进行日期计算时使用公历,并且不受闰秒影响。
time 包内部存在两种时间:wall clock 和 monotonic clock。
- wall clock:真实世界的时间,主要用于时间格式化。
- monotonic clock:单调递增的理想时间,主要用于时间计算。
time 包并没有为两种时间各提供一套 API,而是统一存储在 time.Time 类中。
比如打印当前时间
time.Now()
,控制台上打印出来的字符串是2021-12-14 22:06:20.5518448 +0800 CST m=+0.006418601
。前面的部分是 wall time(真实世界的时间),后面的部分
m=+0.006418601
是 monotonic clock(单调递增的理想时间),表示代码执行到这里时,经过了多少纳秒。尽管在 time 包内存在两种时间,但是使用 time 包时无感知,不了解内部细节也没关系。
Package time provides functionality for measuring and displaying time.
The calendrical calculations always assume a Gregorian calendar, with no leap seconds.
————————
time 包提供了测量和显示时间的能力。
日期计算时,使用 Gregorian calendar(公历),并且没有闰秒。
Monotonic Clocks
Operating systems provide both a “wall clock,” which is subject to changes for clock synchronization, and a “monotonic clock,” which is not. The general rule is that the wall clock is for telling time and the monotonic clock is for measuring time. Rather than split the API, in this package the Time returned by time.Now contains both a wall clock reading and a monotonic clock reading; later time-telling operations use the wall clock reading, but later time-measuring operations, specifically comparisons and subtractions, use the monotonic clock reading.
————————
时间单调增长的时钟。(后文翻译将继续使用 monotonic clock 这个英文说法)
操作系统提供了两种 clock:一种是 wall clock,它跟真实世界的时间同步;一种是 monotonic clock,它的时间纯单调递增。 通常来讲,wall clock 用于展示时间,monotonic clock 用于计算时间。 本 time 包并没有割裂成两套 API,而是通过
time.Now
这一同时包含 wall clock 和 monotonic clock 的方法返回时间。time.Now
内部使用 wall clock 来展示时间,使用 monotonic clock 来计算时间,尤其是用于比较时间和计算时间差值。
For example, this code always computes a positive elapsed time of approximately 20 milliseconds, even if the wall clock is changed during the operation being timed:
start := time.Now()
... operation that takes 20 milliseconds ...
t := time.Now()
elapsed := t.Sub(start)
Other idioms, such as time.Since(start), time.Until(deadline), and time.Now().Before(deadline), are similarly robust against wall clock resets.
————————
举个例子,下面这段代码将计算出大概 20 微秒的时间,即使在计算过程中 wall clock 变化了,算出来的结果也不会变化。
(代码略)
还有些类似的 API,比如
time.Since(start)
、time.Until(deadline)
、time.Now().Before(deadline)
,都不会随 wall clock 变化而变化
The rest of this section gives the precise details of how operations use monotonic clocks, but understanding those details is not required to use this package.
The Time returned by time.Now contains a monotonic clock reading.
If Time t has a monotonic clock reading, t.Add adds the same duration to both the wall clock and monotonic clock readings to compute the result.
Because t.AddDate(y, m, d), t.Round(d), and t.Truncate(d) are wall time computations, they always strip any monotonic clock reading from their results.
Because t.In, t.Local, and t.UTC are used for their effect on the interpretation of the wall time, they also strip any monotonic clock reading from their results.
The canonical way to strip a monotonic clock reading is to use t = t.Round(0).
————————
接下来要讲的部分,会讲清楚 time 包使用 monotonic clocks 的细节,但是单纯使用 time 包的话,可以不必知道这些细节。
(下面这段没翻译明白,因为我没看明白 = =)
使用
time.Now
方法返回的时间对象,内部包含了 monotonic clock 时间。如果一个时间对象 t 内部包含 monotonic clock,那么使用t.add
方法增加时间时,会同时往 t 的 wall clock 和 monotonic clock 增加这段时间。因为t.AddDate(y, m, d)
、t.Round(d)
、t.Truncate(d)
都是在计算 wall time,它们会在结果中删掉 monotonic clock。因为t.In
、t.Local
、t.UTC
是用于解释 wall time 的,它们也会在结果中删掉 monotonic clock。删掉 monotonic clock 的规范写法是t = t.Round(0)
。
If Times t and u both contain monotonic clock readings, the operations t.After(u), t.Before(u), t.Equal(u), and t.Sub(u) are carried out using the monotonic clock readings alone, ignoring the wall clock readings. If either t or u contains no monotonic clock reading, these operations fall back to using the wall clock readings.
On some systems the monotonic clock will stop if the computer goes to sleep.
On such a system, t.Sub(u) may not accurately reflect the actual time that passed between t and u.
————————
如果时间对象 t 和 u 都包含 monotonic clock 时间,那么在使用
t.After(u)
、t.Before(u)
、t.Equal(u)
、t.Sub(u)
时,都只会使用 monotonic clock 计算。如果时间对象 t 和 u 只要有一个没有 monotonic clock 时间,那么将使用 wall clock 计算。有些系统在睡眠时会停止 monotonic clock。在这些系统上,
t.Sub(u)
算出的结果不准。
Because the monotonic clock reading has no meaning outside the current process, the serialized forms generated by t.GobEncode, t.MarshalBinary, t.MarshalJSON, and t.MarshalText omit the monotonic clock reading, and t.Format provides no format for it. Similarly, the constructors time.Date, time.Parse, time.ParseInLocation, and time.Unix, as well as the unmarshalers t.GobDecode, t.UnmarshalBinary.
t.UnmarshalJSON, and t.UnmarshalText always create times with no monotonic clock reading.
Note that the Go == operator compares not just the time instant but also the Location and the monotonic clock reading. See the documentation for the Time type for a discussion of equality testing for Time values.
documentation for the Time type for a discussion of equality testing for Time values.
For debugging, the result of t.String does include the monotonic clock reading if present. If t != u because of different monotonic clock readings, that difference will be visible when printing t.String() and u.String().
————————
因为 monotonic clock 时间在当前进程外没有任何意义,因此
t.GobEncode
、t.MarshalBinary
、t.MarshalJSON
、t.MarshalText
这些序列化方法,将会省略 monotonic clock。同样地,time.Date
、time.Parse
、time.ParseInLocation
、time.Unix
这些构造方法,以及t.GobDecode
、t.UnmarshalBinary
、t.UnmarshalJSON
、t.UnmarshalText
这些反序列化方法,创建出的时间对象也不会包含 monotonic clock 时间。需要注意的是,在 Go 中
==
操作符不光会比较时间戳,也会比较时区和 monotonic clock 时间。跟时间相等相关的部分,参阅time.Time
类型的介绍。在调试时,如果时间对象 t 内部存在 monotonic clock 时间,
t.String
方法会返回该时间。如果两个时间对象 t 和 u 因为 monotonic clock 不同而不同,可以通过t.String()
和u.String()
方法显示出来。
鉴于 time.Time 类也很重要,这个类的注释也过一遍。
使用 time.Time 时,不要使用指针,因为时间创建之后就不应该被更改。
序列化时间的方法都不并发安全,其他方法都并发安全。
time.Time 类内部有时区(Location)信息,修改时间的时区时,不会更改时间戳,只会更改时区信息。
举个例子:
t := time.Date(2021, time.January, 1, 0, 0, 0, 0, time.Local)
,打印下列两个变量。t
:2021-01-01 00:00:00 +0800 CSTt.UTC()
:2020-12-31 16:00:00 +0000 UTC
不要使用 time.Time 对象作为 map 或者数据库的 key。
因为比较 time.Time,除了比较时间戳,还会比较时区以及 monotonic clock。
如果一定要使用 time.Time 作为 key,记得抹除掉时区信息和 monotonic clock 信息。
A Time represents an instant in time with nanosecond precision.
Programs using times should typically store and pass them as values, not pointers. That is, time variables and struct fields should be of type time.Time, not *time.Time.
——————————
time.Time
类代表着一个精度到纳秒的时间戳(time instant)。程序在存储和传递时间对象时,应该使用值类型,而非指针类型。因此,在使用 time 变量和结构体时,应当使用
time.Time
,而非*time.Time
。
A Time value can be used by multiple goroutines simultaneously except that the methods GobDecode, UnmarshalBinary, UnmarshalJSON and UnmarshalText are not concurrency-safe.
Time instants can be compared using the Before, After, and Equal methods.
The Sub method subtracts two instants, producing a Duration.
The Add method adds a Time and a Duration, producing a Time.
————————
time.Time 对象在多 goroutine 环境下,除了
GobDecode
、UnmarshalBinary
、UnmarshalJSON
和UnmarshalText
这四个方法下不是并发安全之外,其余都并发安全。时间戳可以使用
Before
、After
、Equal
方法进行比较。Sub
方法对两个time.Time
相减,得到一个time.Duration
。Add
方法把time.Time
和time.Duration
相加,得到一个time.Time
。
The zero value of type Time is January 1, year 1, 00:00:00.000000000 UTC.
As this time is unlikely to come up in practice, the IsZero method gives a simple way of detecting a time that has not been initialized explicitly.
Each Time has associated with it a Location, consulted when computing the presentation form of the time, such as in the Format, Hour, and Year methods.
The methods Local, UTC, and In return a Time with a specific location.
Changing the location in this way changes only the presentation; it does not change the instant in time being denoted and therefore does not affect the computations described in earlier paragraphs.
————————
每一个
time.Time
对象会关联时区(Location)信息,用于表示时间格式,例如在Format
、Hour
、Year
方法中会使用。Local
、UTC
、In
方法会返回一个包含指定时区的time.Time
对象。以这些方法修改时区,只会修改时间显示,不会修改时间戳,也不会影响之前已有的时间。
time.Time
的零值是公元 1 年 1 月 1 日,00:00:00.000000000 UTC
。由于这个时间在实际场景中不太可能出现,因此提供IsZero
方法检测time.Time
对象是否被显式初始化。
Representations of a Time value saved by the GobEncode, MarshalBinary, MarshalJSON, and MarshalText methods store the Time.Location’s offset, but not the location name. They therefore lose information about Daylight Saving Time.
In addition to the required “wall clock” reading, a Time may contain an optional reading of the current process’s monotonic clock, to provide additional precision for comparison or subtraction.
See the “Monotonic Clocks” section in the package documentation for details.
————————
使用
GobEncode
、MarshalBinary
、MarshalJSON
和MarshalText
方法存储时间,只会包含时区偏移量(Time.Location’s offset),不会存储时区名称。因此这些方法会丢失夏令时信息。
time.Time
除了包含必需的 wall clock 之外,还可能会包含当前程序的 monotonic clock,用于精确地比较时间、计算时间差值。有关 monotonic clock 的部分可以阅读 time 包的文档。
Note that the Go == operator compares not just the time instant but also the Location and the monotonic clock reading. Therefore, Time values should not be used as map or database keys without first guaranteeing that the identical Location has been set for all values identical Location has been set for all values, which can be achieved through use of the UTC or Local method, and that the monotonic clock reading has been stripped by setting t = t.Round(0). In general, prefer t.Equal(u) to t == u, since t.Equal uses the most accurate comparison available and correctly handles the case when only one of its arguments has a monotonic clock reading.
————————
需要注意的是,在 Go 中
==
操作符不光会比较时间戳,也会比较时区和 monotonic clock 时间。因此,如果没有使用UTC
或Local
方法确保时区相同,或者没有使用t.Round(0)
去除掉 monotonic clock 时间,不应该使用 time.Time 作为 map 或数据库的 key。通常情况下,优先使用t.Equal(u)
,而不是t == u
,因为t.Equal
方法更精确,并且能正确处理单 monotonic clock 的 case。
来看 time 包的 API 吧。
贴代码贴累了,就不按功能一段段粘贴了,一口气都贴过来。
1 | // —————————————— 获取 time.Time 对象 —————————————— |