diff --git a/src/time/zoneinfo.go b/src/time/zoneinfo.go index 4e2a207200..4424b44106 100644 --- a/src/time/zoneinfo.go +++ b/src/time/zoneinfo.go @@ -272,7 +272,7 @@ var zoneinfoOnce sync.Once // // The time zone database needed by LoadLocation may not be // present on all systems, especially non-Unix systems. -// LoadLocation looks in the directory, uncompressed zip file, or tzdata file +// LoadLocation looks in the directory or uncompressed zip file // named by the ZONEINFO environment variable, if any, then looks in // known installation locations on Unix systems, // and finally looks in $GOROOT/lib/time/zoneinfo.zip. @@ -292,13 +292,14 @@ func LoadLocation(name string) (*Location, error) { env, _ := syscall.Getenv("ZONEINFO") zoneinfo = &env }) - sources := zoneSources if *zoneinfo != "" { - sources = make([]string, len(zoneSources)+1) - sources[0] = *zoneinfo - copy(sources[1:], zoneSources) + if zoneData, err := loadTzinfoFromDirOrZip(*zoneinfo, name); err == nil { + if z, err := newLocationFromTzinfo(name, zoneData); err == nil { + return z, nil + } + } } - return loadLocation(name, sources) + return loadLocation(name, zoneSources) } // containsDotDot reports whether s contains "..". diff --git a/src/time/zoneinfo_android.go b/src/time/zoneinfo_android.go index 677b06e7e5..40c8ae04ea 100644 --- a/src/time/zoneinfo_android.go +++ b/src/time/zoneinfo_android.go @@ -9,6 +9,7 @@ package time import ( + "errors" "runtime" ) @@ -22,3 +23,57 @@ func initLocal() { // TODO(elias.naur): getprop persist.sys.timezone localLoc = *UTC } + +func init() { + loadTzinfoFromTzdata = androidLoadTzinfoFromTzdata +} + +func androidLoadTzinfoFromTzdata(file, name string) ([]byte, 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 buf, nil + } + return nil, errors.New("cannot find " + name + " in tzdata file " + file) +} diff --git a/src/time/zoneinfo_read.go b/src/time/zoneinfo_read.go index 22658fc28a..eaaaf1f2b4 100644 --- a/src/time/zoneinfo_read.go +++ b/src/time/zoneinfo_read.go @@ -220,6 +220,18 @@ func newLocationFromTzinfo(name string, Tzinfo []byte) (*Location, error) { return l, nil } +// loadTzinfoFromDirOrZip returns the contents of the file with the given name +// in dir. dir can either be an uncompressed zip file, or a directory. +func loadTzinfoFromDirOrZip(dir, name string) ([]byte, error) { + if len(dir) > 4 && dir[len(dir)-4:] == ".zip" { + return loadTzinfoFromZip(dir, name) + } + if dir != "" { + name = dir + "/" + name + } + return readFile(name) +} + // There are 500+ zoneinfo files. Rather than distribute them all // individually, we ship them in an uncompressed zip file. // Used this way, the zip file format serves as a commonly readable @@ -351,61 +363,13 @@ func loadTzinfoFromZip(zipfile, name string) ([]byte, error) { return buf, nil } - return nil, syscall.ENOENT + return nil, errors.New("cannot find " + name + " in zip file " + zipfile) } // loadTzinfoFromTzdata returns the time zone information of the time zone // with the given name, from a tzdata database file as they are typically // found on android. -func loadTzinfoFromTzdata(file, name string) ([]byte, 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 buf, nil - } - return nil, syscall.ENOENT -} +var loadTzinfoFromTzdata func(file, name string) ([]byte, error) // loadTzinfo returns the time zone information of the time zone // with the given name, from a given source. A source may be a @@ -414,13 +378,8 @@ func loadTzinfoFromTzdata(file, name string) ([]byte, error) { func loadTzinfo(name string, source string) ([]byte, error) { if len(source) >= 6 && source[len(source)-6:] == "tzdata" { return loadTzinfoFromTzdata(source, name) - } else if len(source) > 4 && source[len(source)-4:] == ".zip" { - return loadTzinfoFromZip(source, name) } - if source != "" { - name = source + "/" + name - } - return readFile(name) + return loadTzinfoFromDirOrZip(source, name) } // loadLocation returns the Location with the given name from one of