Go中时间与时区问题的深入讲解
来源:脚本之家
时间:2023-01-09 15:13:05 120浏览 收藏
对于一个Golang开发者来说,牢固扎实的基础是十分重要的,golang学习网就来带大家一点点的掌握基础知识点。今天本篇文章带大家了解《Go中时间与时区问题的深入讲解》,主要介绍了go时间、时区,希望对大家的知识积累有所帮助,快点收藏起来吧,否则需要时就找不到了!
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号,墨西哥卖出了多少货。
好了,不扯蛋了。下面是有需要转时区的写法。
var cstZone = time.FixedZone("CST", -7*3600) //设定要转换的时区
h,:=time.ParseDuration("-1h") //中国的时间是+8区 // element t,err:=time.Parse("2006-01-02 15:04:05",item.SaleStartTime)//要处理的时间格式,使用入的字符串要跟格式化的一致 var tString string if err!=nil{ tString=time.Now().In(cstZone).Format("2006-01-02T15:04:05-0700") // 这时有个坑,不需要的自己想加法解决 }else{
t=t.Add(8*h) //要减去+8区 tString=t.In(cstZone).Format("2006-01-02T15:04:05-0700") // 使用时区转化为对应国家的时间。小心格式化的时间,填自己想要的格式。 }
总结
本篇关于《Go中时间与时区问题的深入讲解》的介绍就到此结束啦,但是学无止境,想要了解学习更多关于Golang的相关知识,请关注golang学习网公众号!
-
114 收藏
-
349 收藏
-
150 收藏
-
337 收藏
-
330 收藏
-
309 收藏
-
225 收藏
-
485 收藏
-
233 收藏
-
322 收藏
-
181 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 507次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习
-
- 靓丽的麦片
- 太给力了,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,看完之后很有帮助,总算是懂了,感谢楼主分享文章!
- 2023-03-01 12:54:41
-
- 彩色的小蝴蝶
- 这篇文章真及时,太细致了,受益颇多,已收藏,关注老哥了!希望老哥能多写Golang相关的文章。
- 2023-02-20 09:28:54
-
- 悲凉的香烟
- 太细致了,码起来,感谢作者大大的这篇技术贴,我会继续支持!
- 2023-01-25 20:58:09
-
- 笑点低的自行车
- 这篇技术文章真是及时雨啊,好细啊,很有用,已加入收藏夹了,关注up主了!希望up主能多写Golang相关的文章。
- 2023-01-20 22:28:20
-
- 迷人的金针菇
- 很棒,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,看完之后很有帮助,总算是懂了,感谢大佬分享技术文章!
- 2023-01-17 06:14:07
-
- 忧郁的灯泡
- 太细致了,收藏了,感谢作者大大的这篇博文,我会继续支持!
- 2023-01-11 20:52:46