背景说明
用了很久的时间,但是最近在看《Linux/UNIX系统编程手册》-第10章(时间)以及通读了Go的time包的文档后发现对于常使用的时间可谓是知之甚少,不过目前自认感觉是认知比较清楚了,特此记录
名词解释
- UTC/GMT:通用协调时间/格林威治标准时间(大概也是unix诞生的时间),即1970-01-01 00:00:00
- DST:夏令时 - daylight time,即在高纬度地区,由于夏冬季节的白昼时间相差很大(夏季比冬季长),有些地区会在夏季将时间调快的做法,亚洲和非洲基本都不实施(包括中国)
- CST:China Standard Time,UTC/gMT+0800(即表示在通用协调时间要+8小时)(+0800的意思来源于+hhmm)
- Location:时区,核心构成有 名字(如CST),对于UTC时间的偏移(如+0800),是否使用夏令时,…
概念细化
程序所拥有的时间类型
1. 日历时间 - Wall clock:
- Unix系统本身记录的值都是自UTC以来的秒数和纳秒数(*)
- Go中的Time
- 数据结构:
1
2
3
4
5
6type Time struct {
wall uint64
ext int64
loc *Location // nil location means UTC
} - 结构说明:
- wall的组成(从高到低):第1位flag表示是否有monotonic clock;33位表示秒;30位表示nanoseconds;
- 若无monotonic,则33位必须为0,然后完整的64为的ext存储wall seconds since 1年1月1日;
- 若有monotonic,33位存储自1885年1月1日以来的wall seconds,ext的64位存储有符号的monotonic reading(表示的是nanoseconds since process start)
- loc:指向所用时区信息的指针
- 以fmt.Println(time.Now())为例看看其输出的Local的日历时间(先忽略单调时间部分)是什么样子:
- 2020-10-10 15:19:04.094973 +0800 CST
- 拆分来看就是:
- 日期:2020-10-10
- 时钟:15:19:04.094973
- 所在时区对比UTC的偏移量:+0800
- Local所在时区的名字:CST
- 过程说明:(*)说了系统记录的是自UTC以来的时间戳,那么Go这里是如何输出到本地时间的呢?
- /usr/share/zoneinfo目录下记录了各个时区对应于UTC时间的偏移以及时区的名字
- /etc/localtime是一个link,其链接到/usr/share/zoneinfo目录下的某一个文件,表达的是此计算机的系统本地时间就是此时区
- 结合以上两点就知道了,程序在启动时候通过/etc/localtime获取了Local的时区信息,故对从(*)获取的时间做相应的偏移即可
- 备注:也有办法覆盖/etc/localtime为程序设定特定的时区:设置环境变量TZ(注意,一般此环境变量应该不存在,不存在才会去使用/etc/localtime,若存在且值为空,会使用UTC,否则使用指定的值)
- 数据结构:
- 注意点:程序中的日历时间不一定是单调递增的,如果系统要和某个网络的时间同步、甚至是就是去修改了系统时间那么可能就会导致时间回到过去
- 特别说明两个常用到的规范时间format的标准:
- ISO 8601:
- e.g:2004-05-03T17:30:08+08:00(日期和时间合并表示时,要在时间前面加一大写字母T)
- e.g:2020-10-19T00:15:38Z(如果时间在零时区,并恰好与协调世界时相同,那么(不加空格地)在时间最后加一个大写字母Z。Z是相对协调世界时时间0偏移的代号。)
- RFC 3339:来源于ISO8601并为了简化ISO8601(只有一点点的不同),建议程序员阅读和使用此规范
- 与 ISO 8601不同的地方:
- ISO 8601 permits the hour to be “24”, this profile of ISO 8601 only allows values between “00” and “23” for the hour in order to reduce confusion.
- 总结来说,推荐在格式化表示日期和时间时使用此格式2004-05-03T17:30:08+08:00
- e.g: 在Go中指定使用RFC3339规范的例子(如此只要项目规定好都使用此规范,在解析时就不用去记录2006-01-02 15:04:05这种了,而且抽象度/灵活度更高)
1
2
3
4t, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
fmt.Println(t) // 2006-01-02 15:04:05 +0000 UTC
t, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05+07:00")
fmt.Println(t) // 2006-01-02 15:04:05 +0700 +0700
- 与 ISO 8601不同的地方:
- ISO 8601:
2. 单调时间 - Monotonic clock:
- 可以认为是此程序自开始以来占用的cpu时间(还是精确到纳秒的时间戳),故其一定是单调递增的(单调时间只在同进程内有比较等意义)
- 以go语言为例来看看(以下为在go playground中运行的例子):
- 代码
1
2
3
4
5
6
7
8
9
10package main
import (
"fmt"
"time"
)
func main() {
time.Sleep(time.Second * 2)
fmt.Println(time.Now())
} - 输出:2009-11-10 23:00:02 +0000 UTC m=+2.000000001
- 最后的那个m=+2.000000001即是程序运行的Monotonic clock
- 说明:Go中计算两个Time instance的差值时(t.After(u), t.Before(u), t.Equal(u), t.Sub(u))、如果都有单调时间,则优先使用单调时间
- 代码
- 注意,在Go中如果使用==来比较两个Time实例,在Monotonic clock存在的情况下也会纳入比较!
详细说明
由于我在使用marginnote 3记录笔记的,实际其中还记录了很多更为细节的东西,但是其导出PDF却总是不成功,只能通过导出到ithoughts,但是其中的细节又被变成来注释。权且当目录看吧(完全版可下载附件并用marginnote3打开)
笔记目录
- 此颜色主要知识来自书Linux/UNIX系统编程手册
- 此颜色表示是unix command
- 此颜色表示是Go语言相关的内容(主要来自Go官方time包的文档)