惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Tenable Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
V
Vulnerabilities – Threatpost
G
GRAHAM CLULEY
Simon Willison's Weblog
Simon Willison's Weblog
C
CXSECURITY Database RSS Feed - CXSecurity.com
P
Privacy International News Feed
H
Heimdal Security Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Secure Thoughts
MyScale Blog
MyScale Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
V
Visual Studio Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
L
LINUX DO - 最新话题
D
Darknet – Hacking Tools, Hacker News & Cyber Security
The Cloudflare Blog
美团技术团队
Recorded Future
Recorded Future
T
Tailwind CSS Blog
Latest news
Latest news
Security Archives - TechRepublic
Security Archives - TechRepublic
Security Latest
Security Latest
Know Your Adversary
Know Your Adversary
Cloudbric
Cloudbric
Schneier on Security
Schneier on Security
I
Intezer
L
LINUX DO - 热门话题
P
Palo Alto Networks Blog
云风的 BLOG
云风的 BLOG
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Vercel News
Vercel News
Attack and Defense Labs
Attack and Defense Labs
人人都是产品经理
人人都是产品经理
L
LangChain Blog
爱范儿
爱范儿
博客园 - 三生石上(FineUI控件)
博客园 - 叶小钗
L
Lohrmann on Cybersecurity
S
SegmentFault 最新的问题
W
WeLiveSecurity
C
Cybersecurity and Infrastructure Security Agency CISA
S
Securelist
SecWiki News
SecWiki News
V2EX - 技术
V2EX - 技术
IT之家
IT之家
Cyberwarzone
Cyberwarzone
F
Full Disclosure
Spread Privacy
Spread Privacy
阮一峰的网络日志
阮一峰的网络日志

Origin

Agent 安全:环境变量泄露 人生第一次离职,感觉应该写点什么 在 Python 中复现 Race Condition 再看看 SQL 中的 null 迁移 Zeabur 集群 2025Q3 订阅 Recap 再见 xlog Python 的 pth 文件:在程序启动前自动执行命令 浏览器从 A 到 Z 2024Q3 订阅 Recap 使用 TypeScript 撰写 OmniFocus 脚本 [备忘] Go init 行为 [随笔] 一开始就公布定价 [随笔] Server Action [随笔] Swift 异步:Task vs DispatchQueue [随笔] RSS 与稍后阅读 在 Shell 脚本中嵌入二进制文件 利用 Fly.io 部署 Windmill 强制关闭 xLog 的 Dark Mode 解决 CentOS8 中 yum 源失效 [随笔] 避免完美主义导致的看似时间不够
谈谈时区
Singee · 2023-08-20 · via Origin

通常在本地化时往往会涉及到时区转换的问题,而通常在真正关注到时区之前我们所「默认」使用的时区为 UTC 或“本地”。

本文以 Go 为例,分析下 Go 中的时区使用。

读取时区

在 Go 中,读取时区使用的是 LoadLocation 函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16















func LoadLocation(name string) (*Location, error)

阅读注释可知,如果 name 为空 / UTC 则使用 UTC、为 Local 则使用本地时区(在后面进行讲解),否则,从特定位置进行读取。

所谓读取,是读取的 tzfile 时区文件,可阅读该文档查阅更多信息。简单来说,时区文件是一个以 TZif 开头的二进制文件,其中包含了时区的偏移量、闰秒、夏令时等信息,Go 可以读取相关文件并解析。

  1. 如果存在 ZONEINFO 环境变量,利用该变量指向的目录/压缩文件进行读取
  2. 在 Unix 系统上,使用系统标准位置
  3. (主要用于编译 Go 时)从 $GOROOT/lib/time/zoneinfo.zip 进行读取
  4. (如果 import 了 time/tzdata )从程序嵌入的数据读取

我们比较关注的是 2,即 Unix 的标准时区文件的存储位置。在 Unix 系系统中,时区文件通常存储在 /usr/share/zoneinfo/ 目录中(根据系统不同,还可能是 /usr/share/lib/zoneinfo/ 或 /usr/lib/locale/TZ/),例如,中国(Asia/Shanghai)的时区定义文件就是 /usr/share/zoneinfo/Asia/Shanghai。因此,通常程序可以直接从系统中获取到时区的信息。

注意,在 alpine 环境中,是没有时区定义文件的,因此我们需要特别关注进行处理

  1. 可以在程序中使用 import _ "time/tzdata" 在编译期将时区文件编入程序中,这样在无法找到系统中的时区定义时也可以查找到标准的 IANA 时区定义
  2. 如果我们不需要特别动态的时区,我们可以避免使用 LoadLocation 而是使用 FixedZone 由我们自己提供时区名称和偏移,例如对于中国 UTF+8 可以使用 time.FixedZone("Asia/Shanghai", 8*60*60)

本地时区

通常在我们真正考虑到时区问题之前我们所「默认」使用的时区均为所谓的「本地时区」。

time.Now 为例,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type Time struct {
wall uint64
ext int64

loc *Location
}


func Now() Time {
sec, nsec, mono := now()
mono -= startNano
sec += unixToInternal - minWall
if uint64(sec)>>33 != 0 {



return Time{uint64(nsec), sec + minWall, Local}
}
return Time{hasMonotonic | uint64(sec)<<nsecShift | uint64(nsec), mono, Local}
}

可以看到,Time 结构体的最后一个字段 loc *Location 就是时区,而 time.Now 中使用的时区为 Local

我们本文主要关注时区,如果你对这段代码中的其他因素感兴趣,欢迎阅读 你真的了解 time.Now() 吗?

这里的 Local 就是本地时区,即运行这个程序所在的机器的时区。

1
2
3
4
5
6
7






var Local *Location = &localLoc

阅读 Go 中关于 Local 的说明可知,Go 会优先尊重 TZ 环境变量所指定的时区,如果没有特殊指定,则使用 /etc/localtime 文件读取当前时区。

那么,Local 又是怎么初始化的呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14


var localLoc Location
var localOnce sync.Once

func (l *Location) get() *Location {
if l == nil {
return &utcLoc
}
if l == &localLoc {
localOnce.Do(initLocal)
}
return l
}

从这段代码的逻辑中不难出,Local 并没有真的在程序启动时读取上述信息,而是在首次使用时才真正的通过执行 initLocal 函数来进行初始化。同时,这段代码也隐性的为使用 Location 提出了一个要求:必须调用 get 方法来获取「真正的 Location」。

initLocal 函数在 zoneinfo_*.go 中定义,在不同的机器上有着不同的实现,但本质上都是

如果 TZ 内容以 : 开头,则会忽略该冒号

  1. 如果没有指定 TZ 环境变量,阅读 /etc/localtime(通常就是指向了真正时区文件的软链接)
  2. 如果指定的 TZ 环境变量为绝对路径,阅读该文件
  3. 否则按照上文所分析的 LoadLocation 流程进行时区文件的读取

另外,上述 3 步骤如果失败,会 fallback 到使用 UTC 时间

加餐:tzdata

tzdata 详细定义了历史时区的变更情况,包括夏令时、闰秒等,因此 Asia/Shanghai 相比于简单的 GMT+8 更具有通用性、且可正确处理历史数据。

如果你感兴趣,可以利用 zdump Asia/Shanghai -i 查看上海的时区变化,并和使用夏令时的时间 zdump America/Chicago -i 进行对比。