golang系列之-time
time涉及的内容较多,如生成/存储时间、比较时间、获取时间信息、时区、夏令时等,本文仅介绍一些自己感兴趣的地方
日历计算基于格里高利历(公历),1年有365天(闰年有366天)。同时支持墙上时钟(wall clock)和单调时钟(monotonic clock),其中墙上时钟用于时间同步、报时(time-telling),单调时钟用于时间测量(time-measuring)。并不是所有函数都支持单调时钟,如字符串编码/解码函数就会舍弃单调时钟数据
注意:
- 时间精确度:纳秒
- 大部分都是线程安全,除了
- GobDecode
- UnmarshalBinary
- UnmarshalJSON
- UnmarshalText
- 有些系统会在进程休眠时停止单调时钟,会导致一些函数计算异常,如
- Sub
- Since
- Until
- Before
- After
- Add
- Equal
- Compare
- 字符串编码时,保存的是Location的offset,会导致dst-夏令时丢失,相关函数
- GobEncode
- MarshalBinary
- AppendBinary
- MarshalJSON
- MarshalText
- AppendText
- 字符串编码/解码时,会丢弃单调时钟信息
当前go版本:1.24
快速上手
1 | package main |
运行结果如下所示
1 | go run main.go |
数据结构
时间由Time数据结构表示,具体如下
1 | // src/time/time.go |
时间存储
Time结构使用wall和ext两个字段来存储时间,具体表示如下
- 当wall第一位即flag为0时,表示没有单调时钟数据,wall和ext存储内容如下
1 | // wall -> | 1bit | 33bit | 30bit | |
其中,abc伪数据表示秒数,从1年1月1日开始算起;def伪数据表示纳秒数,范围[0, 999999999]
- 当wall第一位为1时,表示有单调时钟数据,wall和ext存储内容如下
1 | // wall -> | 1bit | 33bit | 30bit | |
其中,abc伪数据表示秒数,def伪数据表示纳秒数,范围[0, 999999999],xyz为单调时钟的纳秒数,从进程启动开始计时
此时,墙上时钟能表示的时间范围是[1885, 2157]
获取时间对象Time
当前时刻Now
Now返回系统时间,返回的Time一般包含单调时钟数据
具体逻辑
- 从系统获取时间数据(通过VDSO或clock_gettime获取)
- 如果返回的时间没有单调时钟数据,按墙上时钟格式存储
- 如果秒数出现溢出的情况,按墙上时钟格式存储
- 存储墙上时钟和单调时钟
1 | // 返回本地当前时间 |
Unix时间戳转换
根据秒数、纳秒数返回一个从1970年1月1日开始计时的unix时间戳的Time,该Time不包含单调时钟数据。按参数精度分为Unix/UnixMilli/UnixMicro
1 | // 精度:秒 |
Date获取指定时刻
根据提供的时间信息如:年/月/日/时/分/秒以及时区获得指定时刻,具体逻辑如下
- 规范化后计算出一个unix时间戳
- 根据时区的偏移量调整unix时间戳
- 生成Time示例
1 | func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time { |
增加时长获取指定时刻
Add
时刻t增加时长d,获得指定时刻,该方法会同时更新墙上时钟和单调时钟
1 | func (t Time) Add(d Duration) Time { |
AddDate
Time根据提供的年数/月数/日数获得目标指定时刻,底层实际调用的是Date方法,只更新wall clock
1 | func (t Time) AddDate(years int, months int, days int) Time { |
字符串转换成Time
常用的layout有:
constant | value |
---|---|
DateTime | “2006-01-02 15:04:05” |
DateOnly | “2006-01-02” |
TimeOnly | “15:04:05” |
Parse
解析字符串获得Time对象以及异常信息
1 | func Parse(layout, value string) (Time, error) { |
ParseInLocation
指定时区,解析字符串获得Time对象以及异常信息
1 | func ParseInLocation(layout, value string, loc *Location) (Time, error) { |
计算两个时刻的差值
Sub
计算t时刻-u时刻的差值
1 | func (t Time) Sub(u Time) Duration { |
Since
计算从指定时刻(过去)到现在时刻的差值
1 | func Since(t Time) Duration { |
Until
计算从现在时刻到指定时刻(将来)的差值
1 | func Until(t Time) Duration { |
比较两个时刻的先后顺序
如果两个Time都包含wall clock和mono clock,则只比较mono clock,否则比较wall clock
Before
判断t时刻是否先于u时刻
1 | func (t Time) Before(u Time) bool { |
After
判断t时刻是否晚于u时刻
1 | func (t Time) After(u Time) bool { |
Equal
判断t时刻与u时刻是否相等
注意:时刻比较不要使用==
,因为==
会比较整个Time结构
1 | func (t Time) Equal(u Time) bool { |
IsZero
判断t时刻是否是零值。
注意:零值是UTC时间1年1月1日0时0分0秒
1 | func (t Time) IsZero() bool { |
Compare
比较两个时刻
- 如果t时刻先于u时刻,返回-1
- 如果t时刻晚与u时刻,返回+1
- 如果t时刻与u时刻相等,返回0
1 | func (t Time) Compare(u Time) int { |
时区
转换时区
时区不会改变Time存储的数值,影响的是数据输出/表示
Local
切换为本机时区
1 | func (t Time) Local() Time { |
UTC
切换为UTC时区
1 | var utcLoc = Location{name: "UTC"} // 启动时初始化 |
指定时区
切换为指定时区
1 | func (t Time) In(loc *Location) Time { |
获取报时信息
获取Unix时间戳
获取从1970年1月1日开始到现在的秒数
1 | // 精度:秒 |
提取日期等信息
业务逻辑经常需要判断两个时刻是否处在同一天/同一周等,此外,也需要获取当前周/当前月等的起止时刻,列举如下依赖方法
method | desc |
---|---|
Date | 获取年月日 |
Clock | 获取时分秒 |
ISOWeek | 获取年和周数 |
Year | 获取年 |
Month | 获取月 |
Day | 获取天 |
Hour | 获取时 |
Minute | 获取分 |
Second | 获取秒 |
Round | 获取秒 |
Truncate | 按给定时长截断 |
Round | 按给定时长向上取整 |
示例
1 | // 1. 两个时刻是否是同一天 |