Skip to content

Commit

Permalink
time: load time zones from the system tzdata file on Android
Browse files Browse the repository at this point in the history
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 <hyangah@gmail.com>
Reviewed-by: David Crawshaw <crawshaw@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
  • Loading branch information
Elias Naur committed Aug 23, 2016
1 parent 0df5ab7 commit 80b31c0
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 1 deletion.
12 changes: 12 additions & 0 deletions src/time/export_android_test.go
Original file line number Diff line number Diff line change
@@ -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]
}
}
119 changes: 119 additions & 0 deletions src/time/zoneinfo_android.go
Original file line number Diff line number Diff line change
@@ -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)
}
18 changes: 18 additions & 0 deletions src/time/zoneinfo_android_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
2 changes: 1 addition & 1 deletion src/time/zoneinfo_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 80b31c0

Please sign in to comment.