| // 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) | |
| } |