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


猜你喜欢
thinkphp学习笔记之多表查询
python3实现TCP协议的简单服务器和客户端案例(分享)
Selenium定位浏览器弹窗方法实例总结

python交互式图形编程实例(一)
PyQt5-QDateEdit的简单使用操作
Python的numpy库下的几个小函数的用法(小结)
使用python解析json字段的3种方式实例

Python 6种基本变量操作技巧总结

透明度设置
python中正则表达式 re.findall 用法
Linux下编译安装python3步骤

MySQL中的count(*) 和 count(1) 区别性能对比分析

Python实例解析图像形态学运算技术

Python中可变和不可变对象的深入讲解

python实现健康码查验系统

详解微信小程序之提高应用速度小技巧

Go语言实现钉钉发送通知
python通过socket实现多个连接并实现ssh功能详解

基于pytorch padding=SAME的解决方式

通过实例解析Python RPC实现原理及方法
