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 ,或者说格林威治时间

对于这三个属性,我们后面再继续讲解,尤其是如何使用 wallext 来区分 wall clock 和 monotonic clock

我们回到这个 Time 结构本身

Time 结构说明

非常有意思,Time 结构的官方解释竟然是: 「 Time 结构代表具有纳秒精度的瞬间 」

纳秒精度很好理解,因为源码中有个 ext 属性,就是用来保存这个的

「 瞬间 」 才是让我震惊的,人人都说白驹过隙,不管从哪个角度看,时间都会瞬间的,只有回忆和展望,时间才是持续的...

程序在存储和传递 Time 时应该是用值 ( t Time ) 而不是指针 ( t *Time ),也就是说,Time 类型的变量和结构字段的类型应为 time.Time,而不是 *time.Time

除了方法 GobDecodeUnmarshalBinaryUnmarshalJSONUnmarshalText 不是并发安全之外,多个 goroutine 可以同时使用 Time 值

这就会为什么建议使用值而不是指针的原因,使用值,因为不可修改 Time 变量或结构的属性,也就是多线程安全的。另一方面,从字面解释,Time 既然是表示一个瞬间,那么修改了值就是另一个瞬间,这显然不符合正常的逻辑。

另一方面,既然 Time 表示的是一个瞬间,那么就可以用于比较操作了,比如 t.Beforet.Aftert.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.Formatt.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 两种时钟,结果,还是下一章节来说吧

关于   |   FAQ   |   我们的愿景   |   广告投放   |  博客

  简单教程,简单编程 - IT 入门首选站

Copyright © 2013-2022 简单教程 twle.cn All Rights Reserved.