Golang時(shí)間處理中容易踩的坑分析解決
簡(jiǎn)介
在各個(gè)語(yǔ)言之中都有時(shí)間類型的處理,因?yàn)檫@個(gè)地球是圓的(我仿佛在講廢話),有多個(gè)時(shí)區(qū),每個(gè)時(shí)區(qū)的時(shí)間不一樣,在程序中有必要存在一種方式,或者說一種類型存儲(chǔ)時(shí)間,還可以通過一系列的方法轉(zhuǎn)換成不同國(guó)家的時(shí)間。
上問提到了時(shí)間、時(shí)區(qū),還有一個(gè)概念為兩個(gè)時(shí)間之間的差值,比如小熊每次可以堅(jiān)持1個(gè)小時(shí)(鍛煉),1個(gè)小時(shí)這種時(shí)間形容詞就是時(shí)間間隔。
這就是三種時(shí)間處理的類型。
類型
Time
、Location
、Duration
時(shí)間、時(shí)區(qū)、時(shí)間間隔。它們都在time
包里面。
Time時(shí)間類型
程序中應(yīng)使用 Time
類型值來保存和傳遞時(shí)間,一個(gè)結(jié)構(gòu)體,精確到納秒。里面的變量都是私有的用不到,先不去管他。
type Time struct { sec int64 //秒 nsec int32 //納秒 loc *Location //時(shí)區(qū) }
- 一個(gè)
Time
類型值可以被多個(gè)go
程同時(shí)使用。因?yàn)樗?time.Time
類型,而不是 指針*time.Time
類型。 - 時(shí)間需要初始化:
IsZero
方法提供了檢驗(yàn)時(shí)間是否是顯式初始化。 - 時(shí)區(qū)類型作為
Time
結(jié)構(gòu)體中的一個(gè)字段,標(biāo)記這個(gè)時(shí)間當(dāng)前是哪個(gè)時(shí)區(qū)。
Duration
時(shí)間間隔,兩個(gè)時(shí)間之間的差值,以納秒為單位,最長(zhǎng) 290
年,作為常識(shí)即可。
type Duration int64
時(shí)區(qū)
我們?cè)谑褂胻ime.Time類型一般都是Local時(shí)間,也就是本地時(shí)間,現(xiàn)在就是中國(guó)時(shí)間。
// 本地時(shí)間(如果是在中國(guó),獲取的是東八區(qū)時(shí)間) curLocalTime := time.Now() // UTC時(shí)間 curUTCTime := time.Now().UTC()
time
包提供了 Location
(也就是時(shí)區(qū))的兩個(gè)實(shí)例:Local
和 UTC
。
Local
代表當(dāng)前系統(tǒng)本地時(shí)區(qū);UTC
代表通用協(xié)調(diào)時(shí)間,也就是零時(shí)區(qū)。time
包默認(rèn)(為顯示提供時(shí)區(qū))使用Local
時(shí)區(qū)。- 平時(shí)使用的都是
Local
時(shí)間,數(shù)據(jù)庫(kù)存儲(chǔ)的時(shí)候要注意,一般orm
框架會(huì)自動(dòng)實(shí)現(xiàn)這個(gè)。
默認(rèn)就是Local中國(guó)時(shí)間!
問題:時(shí)區(qū)這個(gè)怎么設(shè)置?傳字符串進(jìn)去嗎?
curLocalTime := time.Now() //這是local curUtcTime := curLocalTime.In(time.UTC) //這是UTC
時(shí)區(qū)特別容易出錯(cuò),Time
我們使用都是本地時(shí)間,但是!坑來了!
小心有坑
timeStr := "2022-01-13 22:32:17" utcTimeObj, err := time.Parse("2006-01-02 15:04:05", timeStr) if err == nil { fmt.Println(utcTimeObj, utcTimeObj.Unix()) }
你猜猜會(huì)輸出什么?返回的竟然是UTC
時(shí)間2022-01-13 22:32:17 +0000 UTC
。這個(gè)經(jīng)常有人出錯(cuò)。解析字符串時(shí),都以協(xié)調(diào)時(shí)UTC
時(shí)間為準(zhǔn)。
還有另一個(gè)辦法,比較穩(wěn)。我們應(yīng)該總是使用 time.ParseInLocation
來解析時(shí)間,并給第三個(gè)參數(shù)傳遞 time.Local
。
localTimeObj, err := time.ParseInLocation("2006-01-02 15:04:05", timeStr, time.Local) if err == nil { fmt.Println(localTimeObj) }
它返回的是time 類型是嗎?沒錯(cuò)!這兩個(gè)返回的都是time類型。
問:這個(gè)會(huì)用在哪個(gè)場(chǎng)景?
好問題,問到點(diǎn)子上了!
時(shí)間解析的使用場(chǎng)景
前后端傳輸json
數(shù)據(jù)的時(shí)候,或者數(shù)據(jù)庫(kù)存儲(chǔ)讀取的時(shí)候。前后端建議使用時(shí)間戳傳輸,不要使用時(shí)間字符串可以大大省心。數(shù)據(jù)庫(kù)如果使用orm的框架,一般是會(huì)自動(dòng)處理時(shí)間存儲(chǔ)。
我們約定好用時(shí)間戳傳遞,總是有一些比較軸的同事一定要用字符串傳輸,你有沒有這樣的同事?如果非要使用字符串傳輸,在傳遞json的時(shí)候就需要反復(fù)的做解析相當(dāng)?shù)牟挥焉啤?/p>
但也不是不能做~~
大家了解過json解析和反解析有哪兩個(gè)方法嗎?有沒有人重寫過 UnmarshalJSON
和 MarshalJSON
。我們來復(fù)習(xí)一下。
書里面的提到在不同辦法的接口,有可能json
字段的類型會(huì)發(fā)生改變,一般做兼容性處理的時(shí)候會(huì)重寫到。
看這個(gè)截圖,字符串轉(zhuǎn)換成結(jié)構(gòu)體,反過來結(jié)構(gòu)體轉(zhuǎn)換成字符串,就是用MarshalJSON
。
type Person struct { Id int64 `json:"id"` Name string `json:"name"` Birthday Time `json:"_"` }
比如一個(gè)結(jié)構(gòu)體,里面有一個(gè)時(shí)間類型,你的前端同事又不傳時(shí)間戳,你就得手動(dòng)轉(zhuǎn)換成時(shí)間類型,或者時(shí)間戳,這個(gè)你自己決定。這里是 Birthday
舉例,我的注解里面用的json
是一個(gè)下劃線,在解析的時(shí)候就不會(huì)寫入。
問:這個(gè)不寫入, 是 json
庫(kù)實(shí)現(xiàn)的,還是自己實(shí)現(xiàn)的?
json
庫(kù)。json
庫(kù)讀取注解,匹配json
中的字段名稱,寫入到結(jié)構(gòu)體中。我的注解里寫成了下劃線,這只是一個(gè)占位符,習(xí)慣上這么寫。你也可以寫成-
中杠線。
我先寫了一個(gè)People
的反解析函數(shù),json.UnmarshalJSON
會(huì)嘗試調(diào)用??唇貓D
- 先解析到匿名結(jié)構(gòu)體變量中,
birthday
字段賦值給了s.Brithday
,其他字段給了s.tmp
s.Birthday
是一個(gè)字符串類型,再把這個(gè)類型轉(zhuǎn)換成時(shí)間類型。- 把
localtime
放到tmp
里面,tmp
就是之前的people
。
所以返回的就是tmp
, 才是我們要的。
*p = People(s.tmp)
最后再創(chuàng)建一個(gè)People
,把tmp
傳遞過去。
【思考題】為什么這里還要?jiǎng)?chuàng)建一個(gè),直接賦值s.tmp
給*p
可以不?(這里我給你們挖了一個(gè)坑)。
我定義的是新類型,并不是創(chuàng)建,實(shí)際上是一個(gè)強(qiáng)制類型轉(zhuǎn)換。哈哈哈,我就是蔫壞。
關(guān)于時(shí)間處理的各種函數(shù)我也列在下面了,大家收藏看就行了。還是剛剛提到的各種完整代碼。喜歡這篇文章的話點(diǎn)個(gè)在看,么么噠。
時(shí)間操作
獲取當(dāng)前時(shí)間
import time func getCurTime() { // 本地時(shí)間(如果是在中國(guó),獲取的是東八區(qū)時(shí)間) curLocalTime := time.Now() // UTC時(shí)間 curUTCTime := time.Now().UTC() fmt.Println(curLocalTime, curUTCTime) }
時(shí)區(qū)設(shè)置
不同國(guó)家(有時(shí)甚至是同一個(gè)國(guó)家內(nèi)的不同地區(qū))使用不同的時(shí)區(qū)。對(duì)于要輸入和輸出時(shí)間的程序來說,必須對(duì)系統(tǒng)所處的時(shí)區(qū)加以考慮。Go 語(yǔ)言使用 Location
來表示地區(qū)相關(guān)的時(shí)區(qū),一個(gè) Location 可能表示多個(gè)時(shí)區(qū)。展開講解 time
包提供了 Location 的兩個(gè)實(shí)例:Local
和 UTC
Local
代表當(dāng)前系統(tǒng)本地時(shí)區(qū);UTC
代表通用協(xié)調(diào)時(shí)間,也就是零時(shí)區(qū)。time
包默認(rèn)(為顯示提供時(shí)區(qū))使用Local
時(shí)區(qū)。- 平時(shí)使用的都是
Local
時(shí)間,數(shù)據(jù)庫(kù)存儲(chǔ)的時(shí)候要注意,一般orm
框架會(huì)自動(dòng)實(shí)現(xiàn)這個(gè)。
func setTimeZone() { curLocalTime := time.Now() curUtcTime := curLocalTime.In(time.UTC) fmt.Println(curUtcTime) }
通常,我們使用 time.Local
即可,偶爾可能會(huì)需要使用 UTC
。在解析時(shí)間時(shí),心中一定記得有時(shí)區(qū)這么回事。當(dāng)你發(fā)現(xiàn)時(shí)間出現(xiàn)莫名的情況時(shí),很可能是因?yàn)闀r(shí)區(qū)的問題,特別是當(dāng)時(shí)間相差 8 小時(shí)時(shí)。
時(shí)間格式化(時(shí)間類型轉(zhuǎn)字符串)
func time2TimeStr() { localTimeStr := time.Now().Format("2006-01-02 15:04:05") // UTC時(shí)間 utcTimeStr := time.Now().UTC().Format("2006-01-02 15:04:05") fmt.Println(localTimeStr, utcTimeStr) }
時(shí)間類型轉(zhuǎn)時(shí)間戳
func getCurTimeStamp() { // 時(shí)間戳,精確到秒 timestamp := time.Now().Unix() // 時(shí)間戳,精確到納秒 timestampNano := time.Now().UnixNano() fmt.Println(timestamp, timestampNano) }
相關(guān)函數(shù)或方法:
- time.Unix(sec, nsec int64) 通過 Unix 時(shí)間戳生成
time.Time
實(shí)例; - time.Time.Unix() 得到 Unix 時(shí)間戳;
- time.Time.UnixNano() 得到 Unix 時(shí)間戳的納秒表示;
時(shí)間戳轉(zhuǎn)時(shí)間類型
func timestamp2Time() { timestamp := time.Now().Unix() localTimeObj := time.Unix(timestamp, 0) fmt.Println(localTimeObj) }
時(shí)間字符串轉(zhuǎn)時(shí)間類型
func timeStr2Time() { timeStr := "2020-01-13 22:32:17" // 返回的是UTC時(shí)間 2020-01-13 22:32:17 +0000 UTC utcTimeObj, err := time.Parse("2006-01-02 15:04:05", timeStr) if err == nil { fmt.Println(utcTimeObj, utcTimeObj.Unix()) } // 返回的是當(dāng)?shù)貢r(shí)間 2020-01-13 22:32:17 +0800 CST localTimeObj, err := time.ParseInLocation("2006-01-02 15:04:05", timeStr, time.Local) if err == nil { fmt.Println(localTimeObj) } }
time.Parse 解析出來的時(shí)區(qū)卻是 time.UTC(可以通過 Time.Location() 函數(shù)知道是哪個(gè)時(shí)區(qū))。在中國(guó),它們相差 8 小時(shí)。 所以,一般的,我們應(yīng)該總是使用 time.ParseInLocation 來解析時(shí)間,并給第三個(gè)參數(shù)傳遞 time.Local。
時(shí)間計(jì)算
獲取時(shí)間類型具體內(nèi)容
t := time.Now() fmt.Println("time.Now():", t) // 2020-10-24 22:10:53.328973 +0800 CST m=+0.006015101 year, month, day := t.Date() fmt.Println("日期:", year, month, day) // 2020 October 24 fmt.Println("一年中的第幾天:", t.YearDay()) // 298 fmt.Println("星期幾:", t.Weekday()) // Saturday fmt.Println("年:", t.Year()) // 2020 fmt.Println("月:", t.Month()) // October fmt.Println("日:", t.Day()) // 24 fmt.Println("時(shí):", t.Hour()) // 22 fmt.Println("分:", t.Minute()) // 10 fmt.Println("秒:", t.Second()) // 53 fmt.Println("納秒:", t.Nanosecond()) // 328973000 fmt.Println("秒時(shí)間戳:", t.Unix()) // 1603548653 fmt.Println("納秒時(shí)間戳:", t.UnixNano()) // 1603548653328973000 fmt.Println("毫秒時(shí)間戳:", t.UnixNano() / 1e6) // 1603548653328
時(shí)間加減
轉(zhuǎn)換為Time類型比較容易做加減。
- 時(shí)間點(diǎn)可以使用 Before、After 和 Equal 方法進(jìn)行比較。
- Sub 方法讓兩個(gè)時(shí)間點(diǎn)相減,生成一個(gè) Duration 類型值(代表時(shí)間段)。
- Add 方法給一個(gè)時(shí)間點(diǎn)加上一個(gè)時(shí)間段,生成一個(gè)新的 Time 類型時(shí)間點(diǎn)。
func addTime() { curTime := time.Now() // 加1秒 addSecondTime := curTime.Add(time.Second * 1) // 加1分鐘 addMinuteTime := curTime.Add(time.Minute * 1) addMinuteTime2 := curTime.Add(time.Second * time.Duration(60*1)) fmt.Println(addSecondTime, addMinuteTime, addMinuteTime2) }
時(shí)間類型中有提前定義固定的時(shí)間長(zhǎng)度常量,比如一小時(shí)的長(zhǎng)度就是time.Hour
t := time.Now() addOneHour := t.Add(time.Hour) addTwoHour := t.Add(2 * time.Hour) fmt.Println("增加1小時(shí):", addOneHour) fmt.Println("增加2小時(shí):", addTwoHour) subTwoHour := t.Add(-2 * time.Hour) fmt.Println("減去2小時(shí):", subTwoHour) addDate := t.AddDate(1, 0, 0) fmt.Println("增加1年:", addDate) // 2021-10-24 22:10:53.328973 +0800 CST subDate := t.AddDate(-1, 0, 0) fmt.Println("減去1年:", subDate) // 2019-10-24 22:10:53.328973 +0800 CST before := t.Before(t.Add(time.Hour)) fmt.Println("before:", before) after := t.After(t.Add(time.Hour)) fmt.Println("after:", after)
時(shí)間間隔(耗時(shí))
t := time.Now() time.Sleep(2e9) // 休眠2秒 delta := time.Now().Sub(t) fmt.Println("時(shí)間差:", delta) // 2.0534341s
時(shí)間取整(向上取整向下取整)
t, _ := time.ParseInLocation("2006-01-02 15:04:05", "2016-06-13 15:34:39", time.Local) // 整點(diǎn)(向下取整) fmt.Println(t.Truncate(1 * time.Hour)) // 整點(diǎn)(最接近) fmt.Println(t.Round(1 * time.Hour)) // 整分(向下取整) fmt.Println(t.Truncate(1 * time.Minute)) // 整分(最接近) fmt.Println(t.Round(1 * time.Minute)) t2, _ := time.ParseInLocation("2006-01-02 15:04:05", t.Format("2006-01-02 15:00:00"), time.Local) fmt.Println(t2)
拓展
json時(shí)間轉(zhuǎn)換
前后端建議使用時(shí)間戳傳輸,不要使用時(shí)間字符串可以大大省心,如果非要使用字符串傳輸,在傳遞json的時(shí)候就需要反復(fù)的做解析相當(dāng)?shù)牟挥焉?,但也不是不能做?方式一、省心方式,重定義時(shí)間類型
type Time time.Time const ( timeFormart = "2006-01-02 15:04:05" ) func (t *Time) UnmarshalJSON(data []byte) (err error) { now, err := time.ParseInLocation(`"`+timeFormart+`"`, string(data), time.Local) *t = Time(now) return } func (t Time) MarshalJSON() ([]byte, error) { b := make([]byte, 0, len(timeFormart)+2) b = append(b, '"') b = time.Time(t).AppendFormat(b, timeFormart) b = append(b, '"') return b, nil } func (t Time) String() string { return time.Time(t).Format(timeFormart) } type Person struct { Id int64 `json:"id"` Name string `json:"name"` Birthday Time `json:"birthday"` }
- 這種時(shí)間重定義了時(shí)間類型
time.Time
為Time
類型,所以在結(jié)構(gòu)體使用的時(shí)候要注意不要用錯(cuò),結(jié)構(gòu)體直接調(diào)用json
的解析反解析方法就可以,傳入字符串類型,解析為時(shí)間類型。
方式二、重寫結(jié)構(gòu)體方法
type Person struct { Id int64 `json:"id"` Name string `json:"name"` Birthday Time `json:"_"` } func (p *People) UnmarshalJSON(b []byte) error { // 定義臨時(shí)類型 用來接受非`json:"_"`的字段 type tmp People // 用中間變量接收json串,tmp以外的字段用來接受`json:"_"`屬性字段 var s = &struct { tmp // string 先接收字符串類型,一會(huì)再轉(zhuǎn)換 Birthday string `json:"birthday"` }{} // 解析 err := json.Unmarshal(b, &s) if err != nil { return err } localTimeObj, err := time.ParseInLocation("2006-01-02 15:04:05", s.Birthday, time.Local) if err == nil { return err } s.tmp.Birthday = localTimeObj // tmp類型轉(zhuǎn)換回People,并賦值 *p = People(s.tmp) return nil }
作業(yè)
- 嘗試寫出時(shí)間戳轉(zhuǎn)字符串的代碼
- 嘗試求上個(gè)月最后一天
以上就是Golang時(shí)間處理中容易踩的坑分析解決的詳細(xì)內(nèi)容,更多關(guān)于Golang時(shí)間處理踩坑解決的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
關(guān)于go平滑重啟庫(kù)overseer實(shí)現(xiàn)原理詳解
這篇文章主要為大家詳細(xì)介紹了關(guān)于go平滑重啟庫(kù)overseer實(shí)現(xiàn)原理,文中的示例代碼講解詳細(xì),具有一定的參考價(jià)值,有需要的小伙伴可以參考下2023-11-11Go語(yǔ)言用map實(shí)現(xiàn)堆棧功能的方法
這篇文章主要介紹了Go語(yǔ)言用map實(shí)現(xiàn)堆棧功能的方法,實(shí)例分析了Go語(yǔ)言使用map操作堆棧的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-02-02Go?Ticker?周期性定時(shí)器用法及實(shí)現(xiàn)原理詳解
這篇文章主要為大家介紹了Go?Ticker?周期性定時(shí)器用法及實(shí)現(xiàn)原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08golang基于errgroup實(shí)現(xiàn)并發(fā)調(diào)用的方法
這篇文章主要介紹了golang基于errgroup實(shí)現(xiàn)并發(fā)調(diào)用,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-09-09Go?WEB框架使用攔截器驗(yàn)證用戶登錄狀態(tài)實(shí)現(xiàn)
這篇文章主要為大家介紹了Go?WEB框架使用攔截器驗(yàn)證用戶登錄狀態(tài)實(shí)現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07golang程序使用alpine編譯出最小arm鏡像實(shí)現(xiàn)
這篇文章主要為大家介紹了golang程序使用alpine編譯出最小arm鏡像,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12goroutine?泄漏和避免泄漏實(shí)戰(zhàn)示例
這篇文章主要為大家介紹了goroutine?泄漏和避免泄漏實(shí)戰(zhàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12Go語(yǔ)言實(shí)現(xiàn)順序存儲(chǔ)的線性表實(shí)例
這篇文章主要介紹了Go語(yǔ)言實(shí)現(xiàn)順序存儲(chǔ)的線性表的方法,實(shí)例分析了Go語(yǔ)言實(shí)現(xiàn)線性表的定義、插入、刪除元素等的使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-03-03