序列化json的时间信息如何unmarshal到golang中的time.Time类型
json的数据类型只有
string
number
object
array
true
false
null
能用来表示时间的只有string类型了
而要把string去unmarshal到golang中的time.Time类型,是有一定格式要求的。string to time.Time需要调用*Time.UnmarshalJSON
// UnmarshalJSON implements the json.Unmarshaler interface.
// The time is expected to be a quoted string in RFC 3339 format.
func (t *Time) UnmarshalJSON(data []byte) error {
// Ignore null, like in the main JSON package.
if string(data) == "null" {
return nil
}
// Fractional seconds are handled implicitly by Parse.
var err error
*t, err = Parse(`"`+RFC3339+`"`, string(data))
return err
}
从源码里不难发现它使用的layout是RFC3339
golang time包内置的标准的layout
const (
ANSIC = "Mon Jan _2 15:04:05 2006"
UnixDate = "Mon Jan _2 15:04:05 MST 2006"
RubyDate = "Mon Jan 02 15:04:05 -0700 2006"
RFC822 = "02 Jan 06 15:04 MST"
RFC822Z = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone
RFC850 = "Monday, 02-Jan-06 15:04:05 MST"
RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST"
RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone
RFC3339 = "2006-01-02T15:04:05Z07:00"
RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
Kitchen = "3:04PM"
// Handy time stamps.
Stamp = "Jan _2 15:04:05"
StampMilli = "Jan _2 15:04:05.000"
StampMicro = "Jan _2 15:04:05.000000"
StampNano = "Jan _2 15:04:05.000000000"
)
关于RFC3339格式
RFC3339格式time-offset可以用"Z"或者time-numoffset表示,只能二选1。"Z"指零时区,time-numoffset指比零时区快慢几时几分
完整时间表示格式:
date-time = full-dateTfull-time
Here are some examples of Internet date/time format.
1985-04-12T23:20:50.52Z
This represents 20 minutes and 50.52 seconds after the 23rd hour of April 12th, 1985 in UTC.
1996-12-19T16:39:57-08:00
This represents 39 minutes and 57 seconds after the 16th hour of
December 19th, 1996 with an offset of -08:00 from UTC (So the time 1996-12-19T16:39:57-08:00
stands for Pacific
Standard Time 1996-12-19T16:39:57
). Note that this is equivalent to 1996-12-20T00:39:57Z in UTC.
也就是说,在RFC3339时间格式中,full-dateTpartial-time
表示的就是当地时间,至于当地究竟是哪里,则通过time-offset来判断
1990-12-31T23:59:60Z
This represents the leap second inserted at the end of 1990.
我们想使用自定义的时间格式并能够让go正确解析,如2006-01-02 15:04:05
或者2006/01/02 15:04:05
,有如下两种方式:
-
主动利用
time.ParseInLocation
做解析。使用time.ParseInLocation(layout, value string, loc *Location) (Time, error)
,它接受一个自定义layout并解析成Time类型的对象,得到Time对象就方便了,例如接下来就可以使用func (t Time) MarshalJSON() ([]byte, error)
得到RFC 3339 format的字符串时间,实现不同格式时间字符串的转换
// Including main package
package main
// Importing fmt and time
import (
"fmt"
"time"
)
// Calling main
func main() {
// ### yyyy-mm-dd hh:mm:ss -> RFC 3339 ###
t1Str := "2022-04-13 16:10:00"
t1, err := time.ParseInLocation("2006-01-02 15:04:05", t1Str, time.Local)
if err != nil {
fmt.Println(err.Error())
return
}
t1Bytes, _ := t1.MarshalJSON()
fmt.Println(string(t1Bytes)) // "2022-04-13T16:10:00Z"
t1RFCBytes, _ := t1.MarshalText()
fmt.Println(string(t1RFCBytes)) // 2022-04-13T16:10:00Z
// ### yyyy/mm/dd hh:mm:ss -> RFC 3339 ###
t2Str := "2022/04/14 16:10:00"
t2, err := time.ParseInLocation("2006/01/02 15:04:05", t2Str, time.Local)
if err != nil {
fmt.Println(err.Error())
return
}
t2Bytes, _ := t2.MarshalJSON()
fmt.Println(string(t2Bytes)) // "2022-04-14T16:10:00Z"
t2RFCBytes, _ := t2.MarshalText()
fmt.Println(string(t2RFCBytes)) // 2022-04-14T16:10:00Z MarshalText和MarshalJSON比少了两个quote
}
see: https://go.dev/play/p/ul6y-_mgsSu
-
自定义时间类型,实现MarshalJSON 和 UnmarshalJSON 接口
time包使用的func (t *Time) UnmarshalJSON(data []byte) error
使用的layout是time.RFC3339,我们重新封装一个Time类型并实现costumed的UnmarshalJSON就可以
写了个demo:
package main
import (
"encoding/json"
"fmt"
"time"
)
const myTimeFormat string = "2006-01-02 15:04:05"
// 声明定制化的MyTime
type MyTime struct {
time.Time
}
// MarshalJSON serialize time to byte
func (m *MyTime) MarshalJSON() ([]byte, error) {
b := make([]byte, 0, len(myTimeFormat)+2)
b = append(b, '"')
b = m.Time.AppendFormat(b, myTimeFormat)
b = append(b, '"')
return b, nil
}
// UnmarshalJSON
func (m *MyTime) UnmarshalJSON(data []byte) error {
// Ignore null and empty value, like in the main JSON package.
if string(data) == "null" || string(data) == `""` {
return nil
}
// Fractional seconds are handled implicitly by Parse.
tt, err := time.Parse(`"`+myTimeFormat+`"`, string(data))
*m = MyTime{tt}
return err
}
// ToString
func (m *MyTime) ToString() string {
return m.Time.Local().Format(myTimeFormat)
}
type MT struct {
Now MyTime `json:"now"`
Then MyTime `json:"then"`
}
func main() {
jsonStr := `{"now": "2022-03-12 13:37:27", "then":"2022-03-13 13:37:27"}`
var mt MT
err := json.Unmarshal(([]byte)(jsonStr), &mt)
if err != nil {
fmt.Println(err)
}
fmt.Printf("%+v\n", mt)
fmt.Println(mt.Now.ToString(), mt.Then.ToString())
}
{Now:2022-03-12 13:37:27 +0000 UTC Then:2022-03-13 13:37:27 +0000 UTC}
2022-03-12 13:37:27 2022-03-13 13:37:27
该demo也扔到了https://go.dev/play/p/Z4rnbX598c9
计算机的时间信息: Wall Clock and Monotonic Clock
Wall Clock
墙上时间。就是某年某月某日某时某分某秒某某毫秒这些信息。Wall Clock的时间是可以reset的,是人为定义的一种时间表述。UTCTime和LocalTime都是为了解析出准确的Wall Clock
Monotonic Clock
单调时间。可以理解为绝对时间,不断流逝的向前走的时间。当计算两个时刻的时间差时,都是使用Monotonic Clock。因为Wall Clock可以被reset,只有使用Monotonic Clock做差才能得到正确的时间间隔
golang的time包给出了例子
// For example, this code always computes a positive elapsed time of
// approximately 20 milliseconds, even if the wall clock is changed during
// the operation being timed:
//
// start := time.Now()
// ... operation that takes 20 milliseconds ...
// t := time.Now()
// elapsed := t.Sub(start)
The general rule is that the wall clock is for telling time and the monotonic clock is for measuring time
网友评论