1. Time and Time Zone
1.1 Time standard
UTC, Universal Time, is the current standard of time and is measured in atomic time.
GMT, Greenwich Mean Time, is the former time standard, which specifies that the sun passes the Royal Greenwich Observatory in the suburbs of London, England, at 12:00 noon each day.
UTC time is more accurate, but if accuracy is not required, the two standards can be considered equivalent.
1.2 Time Zones
From the Greenwich Prime Meridian, one time zone is divided for every 15° interval of longitude to the east or west, so there are 24 time zones in total, 12 to the east and 12 to the west.
However, for administrative convenience, a country or a province is usually divided together. Here are some of the times expressed in UTC:
- UTC-6 (CST - Central North American Standard Time)
- UTC+9 (JST - Japan Standard Time)
- UTC+8 (CT/CST - Central Standard Time)
- UTC+5:30 (IST - Indian Standard Time)
- UTC+3 (MSK - Moscow time zone)
1.3 Local time
Local time is the current system time with time zone, which can be obtained from /etc/localtime
. In fact /etc/localtime
points to a time zone in the zoneinfo directory. Here is the result on MacOS, the path will be different on Linux.
1
2
3
|
ls -al /etc/localtime
lrwxr-xr-x 1 root wheel 39 Apr 26 2021 /etc/localtime -> /var/db/timezone/zoneinfo/Asia/Shanghai
|
2. Time and Serialization in Go
2.1 How Go initializes time zones
- find the TZ variable to get the time zone
- if there is no TZ, then use
/etc/localtime
3. if TZ="", then use UTC
- if TZ="", then use UTC
- when TZ=“foo” or TZ=":foo", use
/usr/share/zoneinfo/foo
if the file foo refers to will be used to initialize the time zone, otherwise use /usr/share/zoneinfo/foo
Here is the source code for the Go implementation.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
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 Serialization of Go time fields
The Time field can be serialized in Go using “encoding/json”, and the time format can be customized using Format. Here is an example.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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))
}
|
Output results:
1
2
3
4
5
|
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 Serializing Time Fields in Go Structs
If you serialize a structure directly with “encoding/json”, you will get a time format like this: 2021-12-07T17:31:08.811045+08:00. You cannot control the time format with the Format function.
So, how to control the time format in the structure? Please see the following example.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
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)
}
|
Output results:
1
2
|
{"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"}
|
The main purpose here is to use the “github.com/json-iterator/go” package to control Go’s serialization of time fields by specifying the time fields with the keys startedat, finishedat, and the serialization using the timeFormat := time.RFC1123Z
format and locale, _ := time.LoadLocation("Asia/Shanghai")
time zone.
3. Setting the time zone in various environments
3.1 In Linux
Execute the command:
1
|
timedatectl set-timezone Asia/Shanghai
|
Or set the TZ environment variable:
1
2
|
TZ='Asia/Shanghai'
export TZ
|
Both can set the time zone.
3.1 In Docker
When creating an image, setting the TZ variable directly in Dockerfile may cause problems.
1
2
3
|
FROM alpine
ENV TZ='Asia/Shanghai'
COPY ./time.go .
|
Reason: Our common Linux systems, such as Ubuntu and CentOS, have time zones stored in the /usr/share/zoneinfo/
directory, while the alpine image does not.
Therefore the alpine image needs to install some additional packages.
1
2
3
4
5
|
FROM alpine
RUN apk add tzdata && \
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone
|
When running the container, you can mount the time zone description file of the host directly.
1
|
docker run -it --rm -v /etc/localtime:/etc/localtime:ro nginx
|
3.2 In Kubernetes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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
|