Go中時(shí)間與時(shí)區(qū)問題的深入講解
1. 時(shí)間與時(shí)區(qū)
1.1 時(shí)間標(biāo)準(zhǔn)
UTC,世界標(biāo)準(zhǔn)時(shí)間,是現(xiàn)在的時(shí)間標(biāo)準(zhǔn),以原子時(shí)計(jì)時(shí)。
GMT,格林威治時(shí)間,是以前的時(shí)間標(biāo)準(zhǔn),規(guī)定太陽每天經(jīng)過位于英國(guó)倫敦郊區(qū)的皇家格林威治天文臺(tái)的時(shí)間為中午 12 點(diǎn)。
UTC 時(shí)間更加準(zhǔn)確,但如果對(duì)精度要求不高,可以視兩種標(biāo)準(zhǔn)等同。
1.2 時(shí)區(qū)劃分
從格林威治本初子午線起,經(jīng)度每向東或者向西間隔 15°,就劃分一個(gè)時(shí)區(qū),因此一共有 24 個(gè)時(shí)區(qū),東、西個(gè) 12 個(gè)。
但為了行政上的方便,通常會(huì)將一個(gè)國(guó)家或者一個(gè)省份劃分在一起。下面是幾個(gè) UTC 表示的時(shí)間:
- UTC-6(CST — 北美中部標(biāo)準(zhǔn)時(shí)間)
- UTC+9(JST — 日本標(biāo)準(zhǔn)時(shí)間)
- UTC+8(CT/CST — 中原標(biāo)準(zhǔn)時(shí)間)
- UTC+5:30(IST — 印度標(biāo)準(zhǔn)時(shí)間)
- UTC+3(MSK — 莫斯科時(shí)區(qū))
1.3 Local 時(shí)間
Local 時(shí)間為當(dāng)前系統(tǒng)的帶時(shí)區(qū)時(shí)間,可以通過 /etc/localtime 獲取。實(shí)際上 /etc/localtime 是指向 zoneinfo 目錄下的某個(gè)時(shí)區(qū)。下面是 MacOS 上的執(zhí)行結(jié)果,Linux 上的路徑會(huì)不一樣:
ls -al /etc/localtime lrwxr-xr-x 1 root wheel 39 Apr 26 2021 /etc/localtime -> /var/db/timezone/zoneinfo/Asia/Shanghai
2. Go 中的時(shí)間及序列化
2.1 Go 如何初始化時(shí)區(qū)
- 查找 TZ 變量獲取時(shí)區(qū)
- 如果沒有 TZ,那么使用 /etc/localtime
- 如果 TZ="",那么使用 UTC
- 當(dāng) TZ=“foo” 或者 TZ=":foo"時(shí),如果 foo 指向的文件將被用于初始化時(shí)區(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 時(shí)間字段的序列化
在 Go 使用 “encoding/json” 可以對(duì) Time 字段進(jìn)行序列化,使用 Format 可以對(duì)時(shí)間格式進(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)體中的時(shí)間字段序列化
在結(jié)構(gòu)體中,如果直接使用 “encoding/json” 對(duì)結(jié)構(gòu)體進(jìn)行序列化,得到的將會(huì)是這樣的時(shí)間格式: 2021-12-07T17:31:08.811045+08:00。無法使用 Format 函數(shù)對(duì)時(shí)間格式進(jìn)行控制。
那么,如何控制結(jié)構(gòu)體中的時(shí)間格式呢?請(qǐng)看如下示例:
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 對(duì)時(shí)間字段的序列化,通過其提供的擴(kuò)展指定 key 為 startedat、finishedat 的時(shí)間字段,指定序列化時(shí)使用 timeFormat := time.RFC1123Z 格式和 locale, _ := time.LoadLocation("Asia/Shanghai") 時(shí)區(qū)。
3. 各種環(huán)境下設(shè)置時(shí)區(qū)
3.1 在 Linux 中
執(zhí)行命令:
timedatectl set-timezone Asia/Shanghai
或者設(shè)置 TZ 環(huán)境變量:
TZ='Asia/Shanghai' export TZ
都可以設(shè)置時(shí)區(qū)。
3.1 在 Docker 中
在制作鏡像時(shí),直接在 Dockerfile 設(shè)置 TZ 變量,可能會(huì)碰到問題:
FROM alpine ENV TZ='Asia/Shanghai' COPY ./time.go .
報(bào)錯(cuò): panic: time: missing Location in call to Time.In
原因: 我們常用的 Linux 系統(tǒng),例如 Ubuntu、CentOS,在 /usr/share/zoneinfo/ 目錄下存放了各個(gè)時(shí)區(qū)而 alpine 鏡像沒有。
因此 alpine 鏡像需要安裝一些額外的包。
FROM alpine
RUN apk add tzdata && \
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone
在運(yùn)行容器時(shí),可以直接掛載主機(jī)的時(shí)區(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ī)上的時(shí)區(qū)文件掛載到 Pod 中。
4. 參考
https://github.com/json-iterator/go?
5.golang時(shí)區(qū)處理
如果要設(shè)定時(shí)區(qū),那么在使用時(shí)間函數(shù)之前,就要設(shè)定時(shí)區(qū)。
那么問題就來了,打個(gè)比喻說。我想在墨西哥5月6號(hào)12點(diǎn)45分時(shí)開始促銷。而我在中國(guó),那么你要設(shè)定了個(gè)什么樣的數(shù)字呢?
墨西哥是西5時(shí)區(qū)-5,中國(guó)是+8時(shí)區(qū),相差13個(gè)時(shí)區(qū),也就是在中國(guó)今天是5.6號(hào),那么墨西哥是5.5號(hào)
也就是說,我今天要設(shè)置5.7號(hào)的時(shí)間嗎?
。。。。。。。。。。。。。
其實(shí)我覺得,是不是直接設(shè)定5.6號(hào)就行了。因?yàn)樵O(shè)定了,那么墨西哥是5.6號(hào)做的促銷,你只要在5.7號(hào)跟進(jìn)就行了。
如果你想要看交易數(shù)據(jù)(按照中國(guó)的時(shí)間來看),那樣才要做轉(zhuǎn)換。也就是中國(guó)時(shí)間5.7號(hào),墨西哥賣出了多少貨。
好了,不扯蛋了。下面是有需要轉(zhuǎn)時(shí)區(qū)的寫法。
var cstZone = time.FixedZone("CST", -7*3600) //設(shè)定要轉(zhuǎn)換的時(shí)區(qū)<br> <br> h,:=time.ParseDuration("-1h") //中國(guó)的時(shí)間是+8區(qū)
// element
t,err:=time.Parse("2006-01-02 15:04:05",item.SaleStartTime)//要處理的時(shí)間格式,使用入的字符串要跟格式化的一致
var tString string
if err!=nil{
tString=time.Now().In(cstZone).Format("2006-01-02T15:04:05-0700") // 這時(shí)有個(gè)坑,不需要的自己想加法解決
}else{<br> t=t.Add(8*h) //要減去+8區(qū)
tString=t.In(cstZone).Format("2006-01-02T15:04:05-0700") // 使用時(shí)區(qū)轉(zhuǎn)化為對(duì)應(yīng)國(guó)家的時(shí)間。小心格式化的時(shí)間,填自己想要的格式。
}
總結(jié)
到此這篇關(guān)于Go中時(shí)間與時(shí)區(qū)問題的文章就介紹到這了,更多相關(guān)Go時(shí)間與時(shí)區(qū)問題內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語言中未知異常捕獲的多種場(chǎng)景與實(shí)用技巧
在Go語言編程中,異常處理是確保程序健壯性的關(guān)鍵環(huán)節(jié),與一些其他編程語言不同,Go沒有傳統(tǒng)的try - catch結(jié)構(gòu)化異常處理機(jī)制,本文將深入探討Go語言中未知異常捕獲的多種場(chǎng)景與實(shí)用技巧,需要的朋友可以參考下2024-11-11
Golang實(shí)現(xiàn)超時(shí)退出的三種方式
這篇文章主要介紹了Golang三種方式實(shí)現(xiàn)超時(shí)退出,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-03-03
Go語言實(shí)戰(zhàn)之詳細(xì)掌握正則表達(dá)式的應(yīng)用與技巧
正則表達(dá)式是一種從左到右與主題字符串匹配的模式,正則表達(dá)式用于替換字符串中的文本,驗(yàn)證表單,基于模式匹配從字符串中提取子字符串等等,這篇文章主要給大家介紹了關(guān)于Go語言實(shí)戰(zhàn)之詳細(xì)掌握正則表達(dá)式的應(yīng)用與技巧,需要的朋友可以參考下2023-12-12
夯實(shí)Golang基礎(chǔ)之?dāng)?shù)據(jù)類型梳理匯總
這篇文章主要8為大家介紹了夯實(shí)Golang基礎(chǔ)之?dāng)?shù)據(jù)類型梳理匯總,有需要的朋友可以借鑒參考下,希望能夠有所幫助2023-10-10
Go語言通過反射實(shí)現(xiàn)獲取各種類型變量的值
反射是程序在運(yùn)行期間獲取變量的類型和值、或者執(zhí)行變量的方法的能力,這篇文章主要為大家講講Go語言通過反射獲取各種類型變量值的方法,需要的可以參考下2023-07-07
Go標(biāo)準(zhǔn)庫strconv實(shí)現(xiàn)string類型與其他基本數(shù)據(jù)類型之間轉(zhuǎn)換
這篇文章主要為大家介紹了Go標(biāo)準(zhǔn)庫strconv實(shí)現(xiàn)string類型與其他基本數(shù)據(jù)類型之間轉(zhuǎn)換示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11

