Go中時間與時區(qū)問題的深入講解
1. 時間與時區(qū)
1.1 時間標(biāo)準(zhǔn)
UTC,世界標(biāo)準(zhǔn)時間,是現(xiàn)在的時間標(biāo)準(zhǔn),以原子時計(jì)時。
GMT,格林威治時間,是以前的時間標(biāo)準(zhǔn),規(guī)定太陽每天經(jīng)過位于英國倫敦郊區(qū)的皇家格林威治天文臺的時間為中午 12 點(diǎn)。
UTC 時間更加準(zhǔn)確,但如果對精度要求不高,可以視兩種標(biāo)準(zhǔn)等同。
1.2 時區(qū)劃分
從格林威治本初子午線起,經(jīng)度每向東或者向西間隔 15°,就劃分一個時區(qū),因此一共有 24 個時區(qū),東、西個 12 個。
但為了行政上的方便,通常會將一個國家或者一個省份劃分在一起。下面是幾個 UTC 表示的時間:
- UTC-6(CST — 北美中部標(biāo)準(zhǔn)時間)
- UTC+9(JST — 日本標(biāo)準(zhǔn)時間)
- UTC+8(CT/CST — 中原標(biāo)準(zhǔn)時間)
- UTC+5:30(IST — 印度標(biāo)準(zhǔn)時間)
- UTC+3(MSK — 莫斯科時區(qū))
1.3 Local 時間
Local 時間為當(dāng)前系統(tǒng)的帶時區(qū)時間,可以通過 /etc/localtime 獲取。實(shí)際上 /etc/localtime 是指向 zoneinfo 目錄下的某個時區(qū)。下面是 MacOS 上的執(zhí)行結(jié)果,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 如何初始化時區(qū)
- 查找 TZ 變量獲取時區(qū)
- 如果沒有 TZ,那么使用 /etc/localtime
- 如果 TZ="",那么使用 UTC
- 當(dāng) TZ=“foo” 或者 TZ=":foo"時,如果 foo 指向的文件將被用于初始化時區(qū),否則使用 /usr/share/zoneinfo/foo
下面是 Go 實(shí)現(xiàn)的源碼:
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 字段進(jìn)行序列化,使用 Format 可以對時間格式進(jìn)行自定義。如下示例:
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)) }
輸出結(jié)果:
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 結(jié)構(gòu)體中的時間字段序列化
在結(jié)構(gòu)體中,如果直接使用 “encoding/json” 對結(jié)構(gòu)體進(jìn)行序列化,得到的將會是這樣的時間格式: 2021-12-07T17:31:08.811045+08:00。無法使用 Format 函數(shù)對時間格式進(jìn)行控制。
那么,如何控制結(jié)構(gòu)體中的時間格式呢?請看如下示例:
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) }
輸出結(jié)果:
{"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 對時間字段的序列化,通過其提供的擴(kuò)展指定 key 為 startedat、finishedat 的時間字段,指定序列化時使用 timeFormat := time.RFC1123Z 格式和 locale, _ := time.LoadLocation("Asia/Shanghai") 時區(qū)。
3. 各種環(huán)境下設(shè)置時區(qū)
3.1 在 Linux 中
執(zhí)行命令:
timedatectl set-timezone Asia/Shanghai
或者設(shè)置 TZ 環(huán)境變量:
TZ='Asia/Shanghai' export TZ
都可以設(shè)置時區(qū)。
3.1 在 Docker 中
在制作鏡像時,直接在 Dockerfile 設(shè)置 TZ 變量,可能會碰到問題:
FROM alpine ENV TZ='Asia/Shanghai' COPY ./time.go .
報(bào)錯: panic: time: missing Location in call to Time.In
原因: 我們常用的 Linux 系統(tǒng),例如 Ubuntu、CentOS,在 /usr/share/zoneinfo/ 目錄下存放了各個時區(qū)而 alpine 鏡像沒有。
因此 alpine 鏡像需要安裝一些額外的包。
FROM alpine RUN apk add tzdata && \ cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ echo "Asia/Shanghai" > /etc/timezone
在運(yùn)行容器時,可以直接掛載主機(jī)的時區(qū)描述文件:
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
這里將主機(jī)上的時區(qū)文件掛載到 Pod 中。
4. 參考
https://github.com/json-iterator/go?
5.golang時區(qū)處理
如果要設(shè)定時區(qū),那么在使用時間函數(shù)之前,就要設(shè)定時區(qū)。
那么問題就來了,打個比喻說。我想在墨西哥5月6號12點(diǎn)45分時開始促銷。而我在中國,那么你要設(shè)定了個什么樣的數(shù)字呢?
墨西哥是西5時區(qū)-5,中國是+8時區(qū),相差13個時區(qū),也就是在中國今天是5.6號,那么墨西哥是5.5號
也就是說,我今天要設(shè)置5.7號的時間嗎?
。。。。。。。。。。。。。
其實(shí)我覺得,是不是直接設(shè)定5.6號就行了。因?yàn)樵O(shè)定了,那么墨西哥是5.6號做的促銷,你只要在5.7號跟進(jìn)就行了。
如果你想要看交易數(shù)據(jù)(按照中國的時間來看),那樣才要做轉(zhuǎn)換。也就是中國時間5.7號,墨西哥賣出了多少貨。
好了,不扯蛋了。下面是有需要轉(zhuǎn)時區(qū)的寫法。
var cstZone = time.FixedZone("CST", -7*3600) //設(shè)定要轉(zhuǎn)換的時區(qū)<br> <br> h,:=time.ParseDuration("-1h") //中國的時間是+8區(qū) // 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{<br> t=t.Add(8*h) //要減去+8區(qū) tString=t.In(cstZone).Format("2006-01-02T15:04:05-0700") // 使用時區(qū)轉(zhuǎn)化為對應(yīng)國家的時間。小心格式化的時間,填自己想要的格式。 }
總結(jié)
到此這篇關(guān)于Go中時間與時區(qū)問題的文章就介紹到這了,更多相關(guān)Go時間與時區(qū)問題內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
GO語言運(yùn)行環(huán)境下載、安裝、配置圖文教程
這篇文章主要介紹了GO語言運(yùn)行環(huán)境下載、安裝、配置圖文教程,需要的朋友可以參考下2017-02-02golang實(shí)現(xiàn)webgis后端開發(fā)的步驟詳解
這篇文章主要介紹如何用golang結(jié)合postgis數(shù)據(jù)庫,使用gin、grom框架實(shí)現(xiàn)后端的MVC的接口搭建,文中有詳細(xì)的流程步驟及代碼示例,需要的朋友可以參考下2023-06-06golang數(shù)據(jù)結(jié)構(gòu)之golang稀疏數(shù)組sparsearray詳解
這篇文章主要介紹了golang數(shù)據(jù)結(jié)構(gòu)之golang稀疏數(shù)組sparsearray的相關(guān)知識,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-09-09