Skip to content

Commit 6d63a74

Browse files
time/tzdata: new package
Importing the time/tzdata package will embed a copy of the IANA timezone database into the program. This will let the program work correctly when the timezone database is not available on the system. It will increase the size of the binary by about 800K. You can also build a program with -tags timetzdata to embed the timezone database in the program being built. Fixes #21881 Fixes #38013 Fixes #38017 Change-Id: Iffddee72a8f46c95fee3bcde43c142d6899d9246 Reviewed-on: https://go-review.googlesource.com/c/go/+/224588 Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Emmanuel Odeke <[email protected]> Reviewed-by: Tobias Klauser <[email protected]>
1 parent 300ed43 commit 6d63a74

File tree

9 files changed

+13386
-0
lines changed

9 files changed

+13386
-0
lines changed

doc/go1.15.html

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,20 @@ <h2 id="runtime">Runtime</h2>
8787

8888
<h2 id="library">Core library</h2>
8989

90+
<h3 id="time/tzdata">New embedded tzdata package</h3>
91+
92+
<p> <!-- CL 224588 -->
93+
Go 1.15 includes a new package,
94+
<a href="/pkg/time/tzdata/"><code>time/tzdata</code></a>,
95+
that permits embedding the timezone database into a program.
96+
Importing this package (as <code>import _ "time/tzdata"</code>)
97+
permits the program to find timezone information even if the
98+
timezone database is not available on the local system.
99+
You can also embed the timezone database by building
100+
with <code>-tags timetzdata</code>.
101+
Either approach increases the size of the program by about 800K.
102+
</p>
103+
90104
<p>
91105
TODO
92106
</p>

lib/time/update.bash

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ rm -f ../../zoneinfo.zip
2828
zip -0 -r ../../zoneinfo.zip *
2929
cd ../..
3030

31+
go generate time/tzdata
32+
3133
echo
3234
if [ "$1" = "-work" ]; then
3335
echo Left workspace behind in work/.

src/go/build/deps_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,9 @@ var pkgDeps = map[string][]string{
166166
"internal/syscall/windows/registry",
167167
"syscall",
168168
"syscall/js",
169+
"time/tzdata",
169170
},
171+
"time/tzdata": {"L0", "syscall"},
170172

171173
"internal/cfg": {"L0"},
172174
"internal/poll": {"L0", "internal/oserror", "internal/race", "syscall", "time", "unicode/utf16", "unicode/utf8", "internal/syscall/windows", "internal/syscall/unix"},

src/time/embed.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright 2020 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// This file is used with build tag timetzdata to embed tzdata into
6+
// the binary.
7+
8+
// +build timetzdata
9+
10+
package time
11+
12+
import _ "time/tzdata"

src/time/tzdata/generate_zipdata.go

Lines changed: 83 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/time/tzdata/tzdata.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Copyright 2020 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:generate go run generate_zipdata.go
6+
7+
// Package tzdata provides an embedded copy of the timezone database.
8+
// If this package is imported anywhere in the program, then if
9+
// the time package cannot find tzdata files on the system,
10+
// it will use this embedded information.
11+
//
12+
// Importing this package will increase the size of a program by about
13+
// 800K.
14+
//
15+
// This package should normally be imported by a program's main package,
16+
// not by a library. Libraries normally shouldn't decide whether to
17+
// include the timezone database in a program.
18+
//
19+
// This package will be automatically imported if you build with
20+
// -tags timetzdata.
21+
package tzdata
22+
23+
import (
24+
"errors"
25+
"syscall"
26+
_ "unsafe" // for go:linkname
27+
)
28+
29+
// registerLoadFromEmbeddedTZData is defined in package time.
30+
//go:linkname registerLoadFromEmbeddedTZData time.registerLoadFromEmbeddedTZData
31+
func registerLoadFromEmbeddedTZData(func(string) (string, error))
32+
33+
func init() {
34+
registerLoadFromEmbeddedTZData(loadFromEmbeddedTZData)
35+
}
36+
37+
// get4s returns the little-endian 32-bit value at the start of s.
38+
func get4s(s string) int {
39+
if len(s) < 4 {
40+
return 0
41+
}
42+
return int(s[0]) | int(s[1])<<8 | int(s[2])<<16 | int(s[3])<<24
43+
}
44+
45+
// get2s returns the little-endian 16-bit value at the start of s.
46+
func get2s(s string) int {
47+
if len(s) < 2 {
48+
return 0
49+
}
50+
return int(s[0]) | int(s[1])<<8
51+
}
52+
53+
// loadFromEmbeddedTZData returns the contents of the file with the given
54+
// name in an uncompressed zip file, where the contents of the file can
55+
// be found in embeddedTzdata.
56+
// This is similar to time.loadTzinfoFromZip.
57+
func loadFromEmbeddedTZData(name string) (string, error) {
58+
const (
59+
zecheader = 0x06054b50
60+
zcheader = 0x02014b50
61+
ztailsize = 22
62+
63+
zheadersize = 30
64+
zheader = 0x04034b50
65+
)
66+
67+
z := zipdata
68+
69+
idx := len(z) - ztailsize
70+
n := get2s(z[idx+10:])
71+
idx = get4s(z[idx+16:])
72+
73+
for i := 0; i < n; i++ {
74+
// See time.loadTzinfoFromZip for zip entry layout.
75+
if get4s(z[idx:]) != zcheader {
76+
break
77+
}
78+
meth := get2s(z[idx+10:])
79+
size := get4s(z[idx+24:])
80+
namelen := get2s(z[idx+28:])
81+
xlen := get2s(z[idx+30:])
82+
fclen := get2s(z[idx+32:])
83+
off := get4s(z[idx+42:])
84+
zname := z[idx+46 : idx+46+namelen]
85+
idx += 46 + namelen + xlen + fclen
86+
if zname != name {
87+
continue
88+
}
89+
if meth != 0 {
90+
return "", errors.New("unsupported compression for " + name + " in embedded tzdata")
91+
}
92+
93+
// See time.loadTzinfoFromZip for zip per-file header layout.
94+
idx = off
95+
if get4s(z[idx:]) != zheader ||
96+
get2s(z[idx+8:]) != meth ||
97+
get2s(z[idx+26:]) != namelen ||
98+
z[idx+30:idx+30+namelen] != name {
99+
return "", errors.New("corrupt embedded tzdata")
100+
}
101+
xlen = get2s(z[idx+28:])
102+
idx += 30 + namelen + xlen
103+
return z[idx : idx+size], nil
104+
}
105+
106+
return "", syscall.ENOENT
107+
}

src/time/tzdata/tzdata_test.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright 2020 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package tzdata
6+
7+
import (
8+
"reflect"
9+
"testing"
10+
"time"
11+
)
12+
13+
var zones = []string{
14+
"Asia/Jerusalem",
15+
"America/Los_Angeles",
16+
}
17+
18+
func TestEmbeddedTZData(t *testing.T) {
19+
for _, zone := range zones {
20+
ref, err := time.LoadLocation(zone)
21+
if err != nil {
22+
t.Errorf("LoadLocation(%q): %v", zone, err)
23+
continue
24+
}
25+
26+
embedded, err := loadFromEmbeddedTZData(zone)
27+
if err != nil {
28+
t.Errorf("loadFromEmbeddedTZData(%q): %v", zone, err)
29+
continue
30+
}
31+
sample, err := time.LoadLocationFromTZData(zone, []byte(embedded))
32+
if err != nil {
33+
t.Errorf("LoadLocationFromTZData failed for %q: %v", zone, err)
34+
continue
35+
}
36+
37+
// Compare the name and zone fields of ref and sample.
38+
// The tx field changes faster as tzdata is updated.
39+
// The cache fields are expected to differ.
40+
v1 := reflect.ValueOf(ref).Elem()
41+
v2 := reflect.ValueOf(sample).Elem()
42+
typ := v1.Type()
43+
nf := typ.NumField()
44+
found := 0
45+
for i := 0; i < nf; i++ {
46+
ft := typ.Field(i)
47+
if ft.Name != "name" && ft.Name != "zone" {
48+
continue
49+
}
50+
found++
51+
if !equal(t, v1.Field(i), v2.Field(i)) {
52+
t.Errorf("zone %s: system and embedded tzdata field %s differs", zone, ft.Name)
53+
}
54+
}
55+
if found != 2 {
56+
t.Errorf("test must be updated for change to time.Location struct")
57+
}
58+
}
59+
}
60+
61+
// equal is a small version of reflect.DeepEqual that we use to
62+
// compare the values of zoneinfo unexported fields.
63+
func equal(t *testing.T, f1, f2 reflect.Value) bool {
64+
switch f1.Type().Kind() {
65+
case reflect.Slice:
66+
if f1.Len() != f2.Len() {
67+
return false
68+
}
69+
for i := 0; i < f1.Len(); i++ {
70+
if !equal(t, f1.Index(i), f2.Index(i)) {
71+
return false
72+
}
73+
}
74+
return true
75+
case reflect.Struct:
76+
nf := f1.Type().NumField()
77+
for i := 0; i < nf; i++ {
78+
if !equal(t, f1.Field(i), f2.Field(i)) {
79+
return false
80+
}
81+
}
82+
return true
83+
case reflect.String:
84+
return f1.String() == f2.String()
85+
case reflect.Bool:
86+
return f1.Bool() == f2.Bool()
87+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
88+
return f1.Int() == f2.Int()
89+
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
90+
return f1.Uint() == f2.Uint()
91+
default:
92+
t.Errorf("test internal error: unsupported kind %v", f1.Type().Kind())
93+
return true
94+
}
95+
}

0 commit comments

Comments
 (0)