blob: 695a8adfaa34a8b8bfb6a225e9d7cbc31186af12 [file] [log] [blame]
// 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)
}