Go 标准库 time 包之 Time 结构 ( 上 )
在 Go 标准库 time 包之 monotonic clocks 章节中,我们不止一次提到 Time
结构,如果你翻开 Go 语言标准文档之 time
包,也会发现 Time 结构是重中之重。
本章节,我们就来了解下这个神秘的 Time 结构,包括它如何区分 wall clock 和 monotonic clocks 两种时钟。
Time
结构源码
如果只看官方文档,可以看到 Time
结构就是一个空结构,没有任何对外的属性
type Time struct { // contains filtered or unexported fields }
但是,如果我们看源码,那就有那么几个属性了,只是这些属性都是小写字母开头,按管理,是不对外访问的。
type Time struct { // wall and ext encode the wall time seconds, wall time nanoseconds, // and optional monotonic clock reading in nanoseconds. // // From high to low bit position, wall encodes a 1-bit flag (hasMonotonic), // a 33-bit seconds field, and a 30-bit wall time nanoseconds field. // The nanoseconds field is in the range [0, 999999999]. // If the hasMonotonic bit is 0, then the 33-bit field must be zero // and the full signed 64-bit wall seconds since Jan 1 year 1 is stored in ext. // If the hasMonotonic bit is 1, then the 33-bit field holds a 33-bit // unsigned wall seconds since Jan 1 year 1885, and ext holds a // signed 64-bit monotonic clock reading, nanoseconds since process start. wall uint64 ext int64 // loc specifies the Location that should be used to // determine the minute, hour, month, day, and year // that correspond to this Time. // The nil location means UTC. // All UTC times are represented with loc==nil, never loc==&utcLoc. loc *Location }
可以看到有三个属性
属性 | 类型 | 说明 |
---|---|---|
wall | uint64 | wall clock 的总秒数,其实就是时间戳,1970 年 1 月 1 日 0 时 0 分 0 秒以来的总秒数 |
ext | int64 | 纳秒数 |
loc | *Location | 时区 ,默认为 nil ,也就是 UTC ,或者说格林威治时间 |
对于这三个属性,我们后面再继续讲解,尤其是如何使用 wall
和 ext
来区分 wall clock 和 monotonic clock
我们回到这个 Time
结构本身
Time
结构说明
非常有意思,Time
结构的官方解释竟然是: 「 Time 结构代表具有纳秒精度的瞬间 」
纳秒精度很好理解,因为源码中有个 ext
属性,就是用来保存这个的
「 瞬间 」 才是让我震惊的,人人都说白驹过隙,不管从哪个角度看,时间都会瞬间的,只有回忆和展望,时间才是持续的...
程序在存储和传递 Time
时应该是用值 ( t Time ) 而不是指针 ( t *Time ),也就是说,Time
类型的变量和结构字段的类型应为 time.Time
,而不是 *time.Time
。
除了方法 GobDecode
,UnmarshalBinary
,UnmarshalJSON
和 UnmarshalText
不是并发安全之外,多个 goroutine 可以同时使用 Time 值
这就会为什么建议使用值而不是指针的原因,使用值,因为不可修改 Time 变量或结构的属性,也就是多线程安全的。另一方面,从字面解释,Time
既然是表示一个瞬间,那么修改了值就是另一个瞬间,这显然不符合正常的逻辑。
另一方面,既然 Time 表示的是一个瞬间,那么就可以用于比较操作了,比如 t.Before
、t.After
、t.Equal
。
也可以两个 Time 相减来获得两个时间之间的持续时间,例如 t.Sub( t2 )
。
还可以在一个时间 ( t ) 上添加一个持续时间,获得另一个时间点 ( t2 ),例如 t.Add( Duration )
既然是时间点,就像一条线段上的点,总有一个最小值,也有一个最大值 ( 时间是无穷大的,只是我们装不装得下,最大值的限制也就是存储的限制 )。最小值为 0
,即格林威治时间 ( UTC ) 1 年 1 月 0 时 0 分 0 纳秒。从某些方面说,就是格林威治时间 1970 年 1 月 1 日 0 时 0 分 0 纳秒。
但不得不说,很少使用到 0 ,所以 0 值一般用于判断 Time 是否初始化,而且,Time
结构还提供了 isZero()
来检测尚未明确初始化的时间。但这个方法也仅限于 Time 内部使用
Location 时区
每个时间都存在一个时区 ( time.Location ),在计算 Time 的表示形式时会参照时区,例如 t.Format
,t.Hour()
和 t.Year()
方法
而方法 t.Local()
、t.UTC()
和 t.In()
则会返回指定时区的 Time
改变一个 Time 的时区,只会影响到该 Time 的表现,并不会实际更改它所表示的时间瞬间,因此并不会影响前面小节中所介绍的那些计算
wall clock vs monotonic clock
一个 Time ,除了必须的 wall clock 读数之外,还可以包含一个可选的,当前进程的 monotonic clock 读数,用于为比较或减法提供个细粒度的精度。
需要注意的是,Go
语言运算符 ==
不仅比较 wall clock ,还会比较 Location
和 monotonic clock 读数。因此,如果没有事先保证为所有值设置了相同的 Location,那么 Time
值不应该用于 map
或数据库的键 ( key )
这可以通过使用 t.UTC()
或 t.Local()
方法实现,并且通过设置 t = t.Round(0)
来剥离 monotonic clock 读数
对于 Time 对象的比较,通常,我们更喜欢 t.Equal(u)
而不是 t == u
,因为 t.Equal()
使用最准确的比较,并且只有当其中一个参数具有 monotonic clock 读数时才能正确处理。
结束语
我去,我以为能够分析下 Time 结构和如何区分 wall clock 和 monotonic clocks 两种时钟,结果,还是下一章节来说吧