y

ywanbing

V1

2022/05/25阅读:14主题:默认主题

golang 时间相关的问题

我们今天主要是来看一看golang time 包的时间应用方式。

在说到计算机处理时钟上,主要的分为2种方式:

  • 墙上时钟(wall time)
  • 单调时钟(monotonic clocks)

两者的一般规则是「wall time」用于告知时间,而「monotonic clock」用于测量时间;除外还有其他的时钟处理方式。

如果你还不了解上面两种时钟的概念,那么我推荐你可以先访问:你真的了解计算机的时间吗?这篇文章详细说明了这两种时钟的来历。

再看下面的内容之前,我会认为你已经知道了什么是 墙上时钟单调时钟

我接下来看一段 time.Time 的介绍(通过机器翻译而来):

Time 表示具有纳秒精度的时间瞬间。

使用时间的程序通常应该将它们作为值而不是指针来存储和传递。 也就是说,时间变量和结构字段的类型应该是 time.Time,而不是 *time.Time。

一个 Time 值可以同时被多个 goroutine 使用,除了 GobDecode、UnmarshalBinary、 UnmarshalJSON 和 UnmarshalText 方法不是并发安全的。

可以使用 Before、After 和 Equal 方法比较时间瞬间。 Sub 方法减去两个瞬间,产生一个持续时间。 Add 方法为一个时间添加一个持续时间,产生一个时间。

Time 类型的零值是 1 月 1 日,第 1 年,00:00:00.000000000 UTC。 由于这个时间在实践中不太可能出现,因此 IsZero 方法提供了一种简单的方法来检测尚未明确初始化的时间。

每个 Time 都与一个 Location 相关联,在计算时间的表示形式时会参考它, 例如在 Format、Hour 和 Year 方法中。 Local、UTC 和 In 方法返回具有特定位置的时间。 以这种方式更改位置只会更改演示文稿; 它不会改变表示的时刻,因此不会影响前面段落中描述的计算。

GobEncode、MarshalBinary、MarshalJSON 和 MarshalText 方法保存的时间值表示存储 Time.Location 的偏移量,但不存储位置名称。因此,他们会丢失有关夏令时的信息。

除了所需的“挂钟”读数外,时间还可以包含当前进程单调时钟的可选读数,以提供额外的比较或减法精度。有关详细信息,请参阅包文档中的“单调时钟”部分。

请注意,Go == 运算符不仅比较时间瞬间,还比较位置和单调时钟读数。因此,在没有首先保证已为所有值设置相同位置的情况下,不应将时间值用作地图或数据库键,这可以通过使用 UTC 或本地方法来实现,并且单调时钟读数已被剥离设置 t = t.Round(0)。一般来说,比起 t == u,更喜欢 t.Equal(u),因为 t.Equal 使用最准确的比较可用并且正确处理只有一个参数具有单调时钟读数的情况。

到这里官方提供的 time.TIme 的介绍已经说完了。我们来一起看一下 Time 的结构是什么样的:

type Time struct {
 wall uint64
 ext  int64

 loc *Location
}

wall 它的最高位包含一个1位标志。表示hasMonotonic,然后是33位用于跟踪秒数;最后是30位,用于跟踪纳秒,范围为[0,999999999]。

如果该hasMonotonic位为0,则33位字段为零,并且ext 存储自1年1月1日以来的完整带符号的64位wall秒数。

如果该hasMonotonic位为1,则33位字段存储自1885年1月1日以来的无符号的wall秒数,而ext保留有符号的64位单调时钟读数,距离进程开始的时间为纳秒。这是大多数代码中通常发生的情况。

我们来通过 time.now() 函数来查看其中的区别:

// Now returns the current local time.
func Now() Time {
 sec, nsec, mono := now() // 返回对应的秒数,纳秒数,单调时钟数
 mono -= startNano
 sec += unixToInternal - minWall
 if uint64(sec)>>33 != 0 { // 判断如果秒数右移33位后大于0,说明不能采用单调时钟。
  return Time{uint64(nsec), sec + minWall, Local} // 按照 `wall` 时钟返回
 }
  
  // 返回 `wall` 时钟和 `monotonic` 时钟信息
 return Time{hasMonotonic | uint64(sec)<<nsecShift | uint64(nsec), mono, Local}
}

通过这个例子我们可以看到在time包中对于时间差的计算基本都会采用单调时钟墙上时钟的兼容。

  • func (t Time) After(u Time) bool {...}
  • func (t Time) Before(u Time) bool {...}
  • func (t Time) Equal(u Time) bool {...}
  • func (t Time) Add(d Duration) Time {...}
  • func (t Time) Sub(u Time) Duration {...}
  • func Since(t Time) Duration {...}
  • func Until(t Time) Duration {...}

对于增加修改时间计算会清除单调时钟,因为后面调用的是 unixTime 函数:

func unixTime(sec int64, nsec int32) Time {
 return Time{uint64(nsec), sec + unixToInternal, Local}
}

不会计算单调时钟的秒数,如下:

  • func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time {...}
  • func Unix(sec int64, nsec int64) Time {...}
  • func UnixMilli(sec int64, nsec int64) Time {...}
  • func UnixMicro(sec int64, nsec int64) Time {...}
  • func (t Time) AddDate(years int, months int, days int) Time {...}

希望看完这些概念和知识对你使用time包的时间更加的熟练。

关注订阅号:

GolangNewbie GO菜鸟

学习更多!

分类:

后端

标签:

Golang

作者介绍

y
ywanbing
V1