Go中时间与时区问题的深入讲解
作者:陈少文 发布时间:2024-02-19 23:34:55
1. 时间与时区
1.1 时间标准
UTC,世界标准时间,是现在的时间标准,以原子时计时。
GMT,格林威治时间,是以前的时间标准,规定太阳每天经过位于英国伦敦郊区的皇家格林威治天文台的时间为中午 12 点。
UTC 时间更加准确,但如果对精度要求不高,可以视两种标准等同。
1.2 时区划分
从格林威治本初子午线起,经度每向东或者向西间隔 15°,就划分一个时区,因此一共有 24 个时区,东、西个 12 个。
但为了行政上的方便,通常会将一个国家或者一个省份划分在一起。下面是几个 UTC 表示的时间:
UTC-6(CST — 北美中部标准时间)
UTC+9(JST — 日本标准时间)
UTC+8(CT/CST — 中原标准时间)
UTC+5:30(IST — 印度标准时间)
UTC+3(MSK — 莫斯科时区)
1.3 Local 时间
Local 时间为当前系统的带时区时间,可以通过 /etc/localtime 获取。实际上 /etc/localtime 是指向 zoneinfo 目录下的某个时区。下面是 MacOS 上的执行结果,Linux 上的路径会不一样:
ls -al /etc/localtime
lrwxr-xr-x 1 root wheel 39 Apr 26 2021 /etc/localtime -> /var/db/timezone/zoneinfo/Asia/Shanghai
2. Go 中的时间及序列化
2.1 Go 如何初始化时区
查找 TZ 变量获取时区
如果没有 TZ,那么使用 /etc/localtime
如果 TZ="",那么使用 UTC
当 TZ=“foo” 或者 TZ=":foo"时,如果 foo 指向的文件将被用于初始化时区,否则使用 /usr/share/zoneinfo/foo
下面是 Go 实现的源码:
tz, ok := syscall.Getenv("TZ")
switch {
case !ok:
z, err := loadLocation("localtime", []string{"/etc"})
if err == nil {
localLoc = *z
localLoc.name = "Local"
return
}
case tz != "":
if tz[0] == ':' {
tz = tz[1:]
}
if tz != "" && tz[0] == '/' {
if z, err := loadLocation(tz, []string{""}); err == nil {
localLoc = *z
if tz == "/etc/localtime" {
localLoc.name = "Local"
} else {
localLoc.name = tz
}
return
}
} else if tz != "" && tz != "UTC" {
if z, err := loadLocation(tz, zoneSources); err == nil {
localLoc = *z
return
}
}
}
2.2 Go 时间字段的序列化
在 Go 使用 “encoding/json” 可以对 Time 字段进行序列化,使用 Format 可以对时间格式进行自定义。如下示例:
package main
import (
"encoding/json"
"fmt"
"time"
)
func main(){
fmt.Println(time.Now())
var a, _ := json.Marshal(time.Now())
fmt.Println(string(a))
a, _ = json.Marshal(time.Now().Format(time.RFC1123))
fmt.Println(string(a))
a, _ = json.Marshal(time.Now().Format("06-01-02"))
fmt.Println(string(a))
}
输出结果:
2021-12-07 16:44:44.874809 +0800 CST m=+0.000070010
"2021-12-07T16:44:44.874937+08:00"
"Tue, 07 Dec 2021 16:44:44 CST"
"00-120-74 16:44:07"
"21-12-07"
2.3 Go 结构体中的时间字段序列化
在结构体中,如果直接使用 “encoding/json” 对结构体进行序列化,得到的将会是这样的时间格式: 2021-12-07T17:31:08.811045+08:00。无法使用 Format 函数对时间格式进行控制。
那么,如何控制结构体中的时间格式呢?请看如下示例:
package main
import (
"fmt"
"strings"
"time"
"unsafe"
"encoding/json"
jsoniter "github.com/json-iterator/go"
)
func main() {
var json2 = NewJsonTime()
var d = struct {
Title string `json:"title"`
StartedAt time.Time `json:"time"`
}{
Title: "this is title",
StartedAt: time.Now(),
}
t1, _ := json.Marshal(d)
fmt.Println(string(t1))
t2, _ := json2.Marshal(d)
fmt.Println(string(t2))
}
func NewJsonTime() jsoniter.API {
var jt = jsoniter.ConfigCompatibleWithStandardLibrary
jt.RegisterExtension(&CustomTimeExtension{})
return jt
}
type CustomTimeExtension struct {
jsoniter.DummyExtension
}
func (extension *CustomTimeExtension) UpdateStructDescriptor(structDescriptor *jsoniter.StructDescriptor) {
for _, binding := range structDescriptor.Fields {
var typeErr error
var isPtr bool
name := strings.ToLower(binding.Field.Name())
if name == "startedat" {
isPtr = false
} else if name == "finishedat" {
isPtr = true
} else {
continue
}
timeFormat := time.RFC1123Z
locale, _ := time.LoadLocation("Asia/Shanghai")
binding.Encoder = &funcEncoder{fun: func(ptr unsafe.Pointer, stream *jsoniter.Stream) {
if typeErr != nil {
stream.Error = typeErr
return
}
var tp *time.Time
if isPtr {
tpp := (**time.Time)(ptr)
tp = *(tpp)
} else {
tp = (*time.Time)(ptr)
}
if tp != nil {
lt := tp.In(locale)
str := lt.Format(timeFormat)
stream.WriteString(str)
} else {
stream.Write([]byte("null"))
}
}}
binding.Decoder = &funcDecoder{fun: func(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
if typeErr != nil {
iter.Error = typeErr
return
}
str := iter.ReadString()
var t *time.Time
if str != "" {
var err error
tmp, err := time.ParseInLocation(timeFormat, str, locale)
if err != nil {
iter.Error = err
return
}
t = &tmp
} else {
t = nil
}
if isPtr {
tpp := (**time.Time)(ptr)
*tpp = t
} else {
tp := (*time.Time)(ptr)
if tp != nil && t != nil {
*tp = *t
}
}
}}
}
}
type funcDecoder struct {
fun jsoniter.DecoderFunc
}
func (decoder *funcDecoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
decoder.fun(ptr, iter)
}
type funcEncoder struct {
fun jsoniter.EncoderFunc
isEmptyFunc func(ptr unsafe.Pointer) bool
}
func (encoder *funcEncoder) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) {
encoder.fun(ptr, stream)
}
func (encoder *funcEncoder) IsEmpty(ptr unsafe.Pointer) bool {
if encoder.isEmptyFunc == nil {
return false
}
return encoder.isEmptyFunc(ptr)
}
输出结果:
{"title":"this is title","time":"2021-12-07T17:31:08.811045+08:00"}
{"title":"this is title","time":"Tue, 07 Dec 2021 17:31:08 +0800"}
这里主要是使用 “github.com/json-iterator/go” 包控制 Go 对时间字段的序列化,通过其提供的扩展指定 key 为 startedat、finishedat 的时间字段,指定序列化时使用 timeFormat := time.RFC1123Z 格式和 locale, _ := time.LoadLocation("Asia/Shanghai") 时区。
3. 各种环境下设置时区
3.1 在 Linux 中
执行命令:
timedatectl set-timezone Asia/Shanghai
或者设置 TZ 环境变量:
TZ='Asia/Shanghai'
export TZ
都可以设置时区。
3.1 在 Docker 中
在制作镜像时,直接在 Dockerfile 设置 TZ 变量,可能会碰到问题:
FROM alpine
ENV TZ='Asia/Shanghai'
COPY ./time.go .
报错: panic: time: missing Location in call to Time.In
原因: 我们常用的 Linux 系统,例如 Ubuntu、CentOS,在 /usr/share/zoneinfo/ 目录下存放了各个时区而 alpine 镜像没有。
因此 alpine 镜像需要安装一些额外的包。
FROM alpine
RUN apk add tzdata && \
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone
在运行容器时,可以直接挂载主机的时区描述文件:
docker run -it --rm -v /etc/localtime:/etc/localtime:ro nginx
3.2 在 Kubernetes 中
apiVersion: v1
kind: Pod
metadata:
name: test
namespace: default
spec:
restartPolicy: OnFailure
containers:
- name: nginx
image: nginx-test
imagePullPolicy: IfNotPresent
volumeMounts:
- name: date-config
mountPath: /etc/localtime
command: ["sleep", "60000"]
volumes:
- name: date-config
hostPath:
path: /etc/localtime
这里将主机上的时区文件挂载到 Pod 中。
4. 参考
https://github.com/json-iterator/go
5.golang时区处理
如果要设定时区,那么在使用时间函数之前,就要设定时区。
那么问题就来了,打个比喻说。我想在墨西哥5月6号12点45分时开始促销。而我在中国,那么你要设定了个什么样的数字呢?
墨西哥是西5时区-5,中国是+8时区,相差13个时区,也就是在中国今天是5.6号,那么墨西哥是5.5号
也就是说,我今天要设置5.7号的时间吗?
。。。。。。。。。。。。。
其实我觉得,是不是直接设定5.6号就行了。因为设定了,那么墨西哥是5.6号做的促销,你只要在5.7号跟进就行了。
如果你想要看交易数据(按照中国的时间来看),那样才要做转换。也就是中国时间5.7号,墨西哥卖出了多少货。
来源:https://www.chenshaowen.com/blog/the-tips-of-time-and-tz-in-go.html


猜你喜欢
- 前天不小心把硬盘格式化了,丢了好多照片,后来用Recuva这款软件成功把文件恢复过来,可是恢复的文件中有好多重复的文件和无法打开的图片,所以
- 顺序表即线性表的顺序存储结构。它是通过一组地址连续的存储单元对线性表中的数据进行存储的,相邻的两个元素在物理位置上也是相邻的。比
- JDBC连接数据库 •创建一个以JDBC连接数据库的程序,包含7个步骤: 1、加载JDBC驱动程序: 在连接数据库之前,首先要加载想要连接的
- Linux Journal 发表了一篇优化 Oracle 数据库的文章,感觉十分的有用。简要介绍其摘要和大家共同分享 Linux 在企业级数
- 本文主要讲解的是表单,这个其实对于做过网站的人来说,并不陌生,而且可以说是最为常用的提交数据的Form表单。本文主要来讲解一下内容:1.基本
- 在 PHP 中实现异步定时多任务消息推送的方式有多种,其中一种常用的方式是使用异步任务队列。以下是一个简单的步骤:安装和配置消息队列服务(如
- 正在看的ORACLE教程是:在ORACLE移动数据库文件。  
- 关于webpack的配置和使用,网上已经有许多文章了,大多是在讲单页应用,当我们需要打包多个html时,事情就变得麻烦起来。怎么在webpa
- 前言什么是树表查询?借助具有特殊性质的树数据结构进行关键字查找。本文所涉及到的特殊结构性质的树包括:二叉排序树。平衡二叉树。使用上述树结构存
- 在Python3环境下,调试实现了《大话设计模式》中简单工厂模式,通过定义单独的工厂类,完成对具体的产品的实例化,参考链接具体实现见代码:#
- 控制流实现控制流这部分代码主要涉及下面几条字节码指令,下面的所有字节码指令都会有一个参数:JUMP_FORWARD,指令完整条指令会将当前执
- python怎么求最大公约数和最小公倍数一、求最大公约数用辗转相除法求最大公约数的算法如下:两个正整数a和b(a>b),它们的最大公约
- 前几天学习了Yolov5,当我想实际将Yolov5实际运用的时候却不知道怎么办了然后我决定对Yolov5的detect.py修改为可以直接调
- 之前关于 Vue 数据绑定原理的一点分析,最近需要回顾,就顺便发到随笔上了在之前实现一个自己的Mvvm中,用 setter 来观测model
- 前言:数据库是大多数 Web 应用的基础设施,只要想把数据存储下来,就离不开数据库,下面将一起学习一下如何给 Flask 应用添加数据库支持
- 在页面中的链接除了常规的方式以外,如果使用javascript,还有很多种方式,下面是一些使用javascript,打开链接的几种方式:1.
- 由于特定需求,最近实验室需要远程连接外地的sql server 2000服务器,最开始怎么连也连不上,出现了很多问题,但是在今天上午,借用实
- 本文实例讲述了Python实现计算字符串中出现次数最多的字符。分享给大家供大家参考,具体如下:1. 看了网上挺多写的方法都没达到我所需要的效
- Sql server聚合函数在实际工作中应对各种需求使用的还是很广泛的,对于聚合函数的优化自然也就成为了一个重点,一个程序优化的好不好直接决
- 本文实例讲述了PHP实现上传文件并存进数据库的方法。分享给大家供大家参考。具体如下:show_add.php文件如下:<?php &n