From: Elias Naur Date: Mon, 27 Jun 2016 19:38:04 +0000 (+0200) Subject: time: load time zones from the system tzdata file on Android X-Git-Tag: go1.8beta1~1699 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=80b31c05e6ae37c09162406590b9e3b99f0fff9b;p=gostls13.git time: load time zones from the system tzdata file on Android Android timezones are in a packed format, different from the separate files of a regular Unix system. This CL contain the necessary code to parse the packed tzdata file and extract time zones from it. It also adds a basic test to ensure the new parser works. Fixes #13581 Change-Id: Idebe73726c3d4c2de89dd6ae1d7d19f975207500 Reviewed-on: https://go-review.googlesource.com/24494 Reviewed-by: Hyang-Ah Hana Kim Reviewed-by: David Crawshaw TryBot-Result: Gobot Gobot --- diff --git a/src/time/export_android_test.go b/src/time/export_android_test.go new file mode 100644 index 0000000000..fa6a058a73 --- /dev/null +++ b/src/time/export_android_test.go @@ -0,0 +1,12 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package time + +func ForceAndroidTzdataForTest(tzdata bool) { + tzdataPaths = origTzdataPaths + if tzdata { + tzdataPaths = tzdataPaths[:1] + } +} diff --git a/src/time/zoneinfo_android.go b/src/time/zoneinfo_android.go new file mode 100644 index 0000000000..695a8adfaa --- /dev/null +++ b/src/time/zoneinfo_android.go @@ -0,0 +1,119 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Parse the "tzdata" packed timezone file used on Android. +// The format is lifted from ZoneInfoDB.java and ZoneInfo.java in +// java/libcore/util in the AOSP. + +package time + +import ( + "errors" + "runtime" +) + +var tzdataPaths = []string{ + "/system/usr/share/zoneinfo/tzdata", + "/data/misc/zoneinfo/current/tzdata", + runtime.GOROOT() + "/lib/time/zoneinfo.zip", +} + +var origTzdataPaths = tzdataPaths + +func forceZipFileForTesting(zipOnly bool) { + tzdataPaths = make([]string, len(origTzdataPaths)) + copy(tzdataPaths, origTzdataPaths) + if zipOnly { + for i := 0; i < len(tzdataPaths)-1; i++ { + tzdataPaths[i] = "/XXXNOEXIST" + } + } +} + +func initTestingZone() { + z, err := loadLocation("America/Los_Angeles") + if err != nil { + panic("cannot load America/Los_Angeles for testing: " + err.Error()) + } + z.name = "Local" + localLoc = *z +} + +func initLocal() { + // TODO(elias.naur): getprop persist.sys.timezone + localLoc = *UTC +} + +func loadLocation(name string) (*Location, error) { + var firstErr error + for _, path := range tzdataPaths { + var z *Location + var err error + if len(path) > 4 && path[len(path)-4:] == ".zip" { + z, err = loadZoneZip(path, name) + } else { + z, err = loadTzdataFile(path, name) + } + if err == nil { + z.name = name + return z, nil + } else if firstErr == nil && !isNotExist(err) { + firstErr = err + } + } + if firstErr != nil { + return nil, firstErr + } + return nil, errors.New("unknown time zone " + name) +} + +func loadTzdataFile(file, name string) (*Location, error) { + const ( + headersize = 12 + 3*4 + namesize = 40 + entrysize = namesize + 3*4 + ) + if len(name) > namesize { + return nil, errors.New(name + " is longer than the maximum zone name length (40 bytes)") + } + fd, err := open(file) + if err != nil { + return nil, err + } + defer closefd(fd) + + buf := make([]byte, headersize) + if err := preadn(fd, buf, 0); err != nil { + return nil, errors.New("corrupt tzdata file " + file) + } + d := data{buf, false} + if magic := d.read(6); string(magic) != "tzdata" { + return nil, errors.New("corrupt tzdata file " + file) + } + d = data{buf[12:], false} + indexOff, _ := d.big4() + dataOff, _ := d.big4() + indexSize := dataOff - indexOff + entrycount := indexSize / entrysize + buf = make([]byte, indexSize) + if err := preadn(fd, buf, int(indexOff)); err != nil { + return nil, errors.New("corrupt tzdata file " + file) + } + for i := 0; i < int(entrycount); i++ { + entry := buf[i*entrysize : (i+1)*entrysize] + // len(name) <= namesize is checked at function entry + if string(entry[:len(name)]) != name { + continue + } + d := data{entry[namesize:], false} + off, _ := d.big4() + size, _ := d.big4() + buf := make([]byte, size) + if err := preadn(fd, buf, int(off+dataOff)); err != nil { + return nil, errors.New("corrupt tzdata file " + file) + } + return loadZoneData(buf) + } + return nil, errors.New("cannot find " + name + " in tzdata file " + file) +} diff --git a/src/time/zoneinfo_android_test.go b/src/time/zoneinfo_android_test.go new file mode 100644 index 0000000000..ba065d10a6 --- /dev/null +++ b/src/time/zoneinfo_android_test.go @@ -0,0 +1,18 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package time_test + +import ( + "testing" + . "time" +) + +func TestAndroidTzdata(t *testing.T) { + ForceAndroidTzdataForTest(true) + defer ForceAndroidTzdataForTest(false) + if _, err := LoadLocation("America/Los_Angeles"); err != nil { + t.Error(err) + } +} diff --git a/src/time/zoneinfo_unix.go b/src/time/zoneinfo_unix.go index ed9502da57..bbf263a16f 100644 --- a/src/time/zoneinfo_unix.go +++ b/src/time/zoneinfo_unix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin,386 darwin,amd64 dragonfly freebsd linux nacl netbsd openbsd solaris +// +build darwin,386 darwin,amd64 dragonfly freebsd linux,!android nacl netbsd openbsd solaris // Parse "zoneinfo" time zone file. // This is a fairly standard file format used on OS X, Linux, BSD, Sun, and others.