Commit 7340d139 authored by Florian Uekermann's avatar Florian Uekermann Committed by Ian Lance Taylor

time: reduce OS dependent timezone related code

Loading and testing timezones is currently implemented using several,
partly redundant, OS specific data structures and functions. This
change merges most of that code into OS independent implementations.

Change-Id: Iae2877c5f48d1e4a9de9ce55d0530d52e24cf96e
Reviewed-on: https://go-review.googlesource.com/64391
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarIan Lance Taylor <iant@golang.org>
parent abd7ba02
...@@ -5,8 +5,8 @@ ...@@ -5,8 +5,8 @@
package time package time
func ForceAndroidTzdataForTest(tzdata bool) { func ForceAndroidTzdataForTest(tzdata bool) {
tzdataPaths = origTzdataPaths forceZipFileForTesting(false)
if tzdata { if tzdata {
tzdataPaths = tzdataPaths[:1] zoneSources = zoneSources[:len(zoneSources)-1]
} }
} }
...@@ -4,9 +4,14 @@ ...@@ -4,9 +4,14 @@
package time package time
func ForceAusForTesting() { func ForceAusFromTZIForTesting() {
ResetLocalOnceForTest() ResetLocalOnceForTest()
localOnce.Do(initAusTestingZone) localOnce.Do(func() { initLocalFromTZI(&aus) })
}
func ForceUSPacificFromTZIForTesting() {
ResetLocalOnceForTest()
localOnce.Do(func() { initLocalFromTZI(&usPacific) })
} }
func ToEnglishName(stdname, dstname string) (string, error) { func ToEnglishName(stdname, dstname string) (string, error) {
......
...@@ -9,6 +9,25 @@ func init() { ...@@ -9,6 +9,25 @@ func init() {
ForceUSPacificForTesting() ForceUSPacificForTesting()
} }
func initTestingZone() {
z, err := loadLocation("America/Los_Angeles", zoneSources[len(zoneSources)-1:])
if err != nil {
panic("cannot load America/Los_Angeles for testing: " + err.Error())
}
z.name = "Local"
localLoc = *z
}
var origZoneSources = zoneSources
func forceZipFileForTesting(zipOnly bool) {
zoneSources = make([]string, len(origZoneSources))
copy(zoneSources, origZoneSources)
if zipOnly {
zoneSources = zoneSources[len(zoneSources)-1:]
}
}
var Interrupt = interrupt var Interrupt = interrupt
var DaysIn = daysIn var DaysIn = daysIn
......
...@@ -78,5 +78,3 @@ func preadn(fd uintptr, buf []byte, off int) error { ...@@ -78,5 +78,3 @@ func preadn(fd uintptr, buf []byte, off int) error {
} }
return nil return nil
} }
func isNotExist(err error) bool { return err == syscall.ENOENT }
...@@ -292,13 +292,14 @@ func LoadLocation(name string) (*Location, error) { ...@@ -292,13 +292,14 @@ func LoadLocation(name string) (*Location, error) {
env, _ := syscall.Getenv("ZONEINFO") env, _ := syscall.Getenv("ZONEINFO")
zoneinfo = &env zoneinfo = &env
}) })
if zoneinfo != nil && *zoneinfo != "" { if *zoneinfo != "" {
if z, err := loadZoneFile(*zoneinfo, name); err == nil { if zoneData, err := loadTzinfoFromDirOrZip(*zoneinfo, name); err == nil {
z.name = name if z, err := newLocationFromTzinfo(name, zoneData); err == nil {
return z, nil return z, nil
}
} }
} }
return loadLocation(name) return loadLocation(name, zoneSources)
} }
// containsDotDot reports whether s contains "..". // containsDotDot reports whether s contains "..".
......
...@@ -13,62 +13,22 @@ import ( ...@@ -13,62 +13,22 @@ import (
"runtime" "runtime"
) )
var tzdataPaths = []string{ var zoneSources = []string{
"/system/usr/share/zoneinfo/tzdata", "/system/usr/share/zoneinfo/tzdata",
"/data/misc/zoneinfo/current/tzdata", "/data/misc/zoneinfo/current/tzdata",
runtime.GOROOT() + "/lib/time/zoneinfo.zip", 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() { func initLocal() {
// TODO(elias.naur): getprop persist.sys.timezone // TODO(elias.naur): getprop persist.sys.timezone
localLoc = *UTC localLoc = *UTC
} }
func loadLocation(name string) (*Location, error) { func init() {
var firstErr error loadTzinfoFromTzdata = androidLoadTzinfoFromTzdata
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) { func androidLoadTzinfoFromTzdata(file, name string) ([]byte, error) {
const ( const (
headersize = 12 + 3*4 headersize = 12 + 3*4
namesize = 40 namesize = 40
...@@ -113,7 +73,7 @@ func loadTzdataFile(file, name string) (*Location, error) { ...@@ -113,7 +73,7 @@ func loadTzdataFile(file, name string) (*Location, error) {
if err := preadn(fd, buf, int(off+dataOff)); err != nil { if err := preadn(fd, buf, int(off+dataOff)); err != nil {
return nil, errors.New("corrupt tzdata file " + file) return nil, errors.New("corrupt tzdata file " + file)
} }
return loadZoneData(buf) return buf, nil
} }
return nil, errors.New("cannot find " + name + " in tzdata file " + file) return nil, errors.New("cannot find " + name + " in tzdata file " + file)
} }
...@@ -9,43 +9,23 @@ package time ...@@ -9,43 +9,23 @@ package time
import "syscall" import "syscall"
var zoneFile string var zoneSources = []string{
getZipParent() + "/zoneinfo.zip",
}
func init() { func getZipParent() string {
wd, err := syscall.Getwd() wd, err := syscall.Getwd()
if err != nil { if err != nil {
return return "/XXXNOEXIST"
} }
// The working directory at initialization is the root of the // The working directory at initialization is the root of the
// app bundle: "/private/.../bundlename.app". That's where we // app bundle: "/private/.../bundlename.app". That's where we
// keep zoneinfo.zip. // keep zoneinfo.zip.
zoneFile = wd + "/zoneinfo.zip" return wd
}
func forceZipFileForTesting(zipOnly bool) {
// On iOS we only have the zip file.
}
func initTestingZone() {
z, err := loadZoneFile(zoneFile, "America/Los_Angeles")
if err != nil {
panic("cannot load America/Los_Angeles for testing: " + err.Error())
}
z.name = "Local"
localLoc = *z
} }
func initLocal() { func initLocal() {
// TODO(crawshaw): [NSTimeZone localTimeZone] // TODO(crawshaw): [NSTimeZone localTimeZone]
localLoc = *UTC localLoc = *UTC
} }
func loadLocation(name string) (*Location, error) {
z, err := loadZoneFile(zoneFile, name)
if err != nil {
return nil, err
}
z.name = name
return z, nil
}
...@@ -11,6 +11,10 @@ import ( ...@@ -11,6 +11,10 @@ import (
"syscall" "syscall"
) )
var zoneSources = []string{
runtime.GOROOT() + "/lib/time/zoneinfo.zip",
}
func isSpace(r rune) bool { func isSpace(r rune) bool {
return r == ' ' || r == '\t' || r == '\n' return r == ' ' || r == '\t' || r == '\n'
} }
...@@ -118,15 +122,6 @@ func loadZoneFilePlan9(name string) (*Location, error) { ...@@ -118,15 +122,6 @@ func loadZoneFilePlan9(name string) (*Location, error) {
return loadZoneDataPlan9(string(b)) return loadZoneDataPlan9(string(b))
} }
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() { func initLocal() {
t, ok := syscall.Getenv("timezone") t, ok := syscall.Getenv("timezone")
if ok { if ok {
...@@ -145,16 +140,3 @@ func initLocal() { ...@@ -145,16 +140,3 @@ func initLocal() {
// Fall back to UTC. // Fall back to UTC.
localLoc.name = "UTC" localLoc.name = "UTC"
} }
func loadLocation(name string) (*Location, error) {
z, err := loadZoneFile(runtime.GOROOT()+"/lib/time/zoneinfo.zip", name)
if err != nil {
return nil, err
}
z.name = name
return z, nil
}
func forceZipFileForTesting(zipOnly bool) {
// We only use the zip file anyway.
}
...@@ -9,7 +9,10 @@ ...@@ -9,7 +9,10 @@
package time package time
import "errors" import (
"errors"
"syscall"
)
// maxFileSize is the max permitted size of files read by readFile. // maxFileSize is the max permitted size of files read by readFile.
// As reference, the zoneinfo.zip distributed by Go is ~350 KB, // As reference, the zoneinfo.zip distributed by Go is ~350 KB,
...@@ -76,8 +79,11 @@ func byteString(p []byte) string { ...@@ -76,8 +79,11 @@ func byteString(p []byte) string {
var badData = errors.New("malformed time zone information") var badData = errors.New("malformed time zone information")
func loadZoneData(bytes []byte) (l *Location, err error) { // newLocationFromTzinfo returns the Location described by Tzinfo with the given name.
d := data{bytes, false} // The expected format for Tzinfo is that of a timezone file as they are found in the
// the IANA Time Zone database.
func newLocationFromTzinfo(name string, Tzinfo []byte) (*Location, error) {
d := data{Tzinfo, false}
// 4-byte magic "TZif" // 4-byte magic "TZif"
if magic := d.read(4); string(magic) != "TZif" { if magic := d.read(4); string(magic) != "TZif" {
...@@ -195,7 +201,7 @@ func loadZoneData(bytes []byte) (l *Location, err error) { ...@@ -195,7 +201,7 @@ func loadZoneData(bytes []byte) (l *Location, err error) {
} }
// Committed to succeed. // Committed to succeed.
l = &Location{zone: zone, tx: tx} l := &Location{zone: zone, tx: tx, name: name}
// Fill in the cache with information about right now, // Fill in the cache with information about right now,
// since that will be the most common lookup. // since that will be the most common lookup.
...@@ -214,18 +220,16 @@ func loadZoneData(bytes []byte) (l *Location, err error) { ...@@ -214,18 +220,16 @@ func loadZoneData(bytes []byte) (l *Location, err error) {
return l, nil return l, nil
} }
func loadZoneFile(dir, name string) (l *Location, err error) { // 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" { if len(dir) > 4 && dir[len(dir)-4:] == ".zip" {
return loadZoneZip(dir, name) return loadTzinfoFromZip(dir, name)
} }
if dir != "" { if dir != "" {
name = dir + "/" + name name = dir + "/" + name
} }
buf, err := readFile(name) return readFile(name)
if err != nil {
return
}
return loadZoneData(buf)
} }
// There are 500+ zoneinfo files. Rather than distribute them all // There are 500+ zoneinfo files. Rather than distribute them all
...@@ -252,7 +256,9 @@ func get2(b []byte) int { ...@@ -252,7 +256,9 @@ func get2(b []byte) int {
return int(b[0]) | int(b[1])<<8 return int(b[0]) | int(b[1])<<8
} }
func loadZoneZip(zipfile, name string) (l *Location, err error) { // loadTzinfoFromZip returns the contents of the file with the given name
// in the given uncompressed zip file.
func loadTzinfoFromZip(zipfile, name string) ([]byte, error) {
fd, err := open(zipfile) fd, err := open(zipfile)
if err != nil { if err != nil {
return nil, errors.New("open " + zipfile + ": " + err.Error()) return nil, errors.New("open " + zipfile + ": " + err.Error())
...@@ -354,8 +360,46 @@ func loadZoneZip(zipfile, name string) (l *Location, err error) { ...@@ -354,8 +360,46 @@ func loadZoneZip(zipfile, name string) (l *Location, err error) {
return nil, errors.New("corrupt zip file " + zipfile) return nil, errors.New("corrupt zip file " + zipfile)
} }
return loadZoneData(buf) return buf, nil
} }
return nil, errors.New("cannot find " + name + " in zip file " + zipfile) 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.
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
// timezone database directory, tzdata database file or an uncompressed
// zip file, containing the contents of such a directory.
func loadTzinfo(name string, source string) ([]byte, error) {
if len(source) >= 6 && source[len(source)-6:] == "tzdata" {
return loadTzinfoFromTzdata(source, name)
}
return loadTzinfoFromDirOrZip(source, name)
}
// loadLocation returns the Location with the given name from one of
// the specified sources. See loadTzinfo for a list of supported sources.
// The first timezone data matching the given name that is successfully loaded
// and parsed is returned as a Location.
func loadLocation(name string, sources []string) (z *Location, firstErr error) {
for _, source := range sources {
var zoneData, err = loadTzinfo(name, source)
if err == nil {
if z, err = newLocationFromTzinfo(name, zoneData); err == nil {
return z, nil
}
}
if firstErr == nil && err != syscall.ENOENT {
firstErr = err
}
}
if firstErr != nil {
return nil, firstErr
}
return nil, errors.New("unknown time zone " + name)
}
...@@ -12,41 +12,19 @@ ...@@ -12,41 +12,19 @@
package time package time
import ( import (
"errors"
"runtime" "runtime"
"syscall" "syscall"
) )
func initTestingZone() {
z, err := loadZoneFile(runtime.GOROOT()+"/lib/time/zoneinfo.zip", "America/Los_Angeles")
if err != nil {
panic("cannot load America/Los_Angeles for testing: " + err.Error())
}
z.name = "Local"
localLoc = *z
}
// Many systems use /usr/share/zoneinfo, Solaris 2 has // Many systems use /usr/share/zoneinfo, Solaris 2 has
// /usr/share/lib/zoneinfo, IRIX 6 has /usr/lib/locale/TZ. // /usr/share/lib/zoneinfo, IRIX 6 has /usr/lib/locale/TZ.
var zoneDirs = []string{ var zoneSources = []string{
"/usr/share/zoneinfo/", "/usr/share/zoneinfo/",
"/usr/share/lib/zoneinfo/", "/usr/share/lib/zoneinfo/",
"/usr/lib/locale/TZ/", "/usr/lib/locale/TZ/",
runtime.GOROOT() + "/lib/time/zoneinfo.zip", runtime.GOROOT() + "/lib/time/zoneinfo.zip",
} }
var origZoneDirs = zoneDirs
func forceZipFileForTesting(zipOnly bool) {
zoneDirs = make([]string, len(origZoneDirs))
copy(zoneDirs, origZoneDirs)
if zipOnly {
for i := 0; i < len(zoneDirs)-1; i++ {
zoneDirs[i] = "/XXXNOEXIST"
}
}
}
func initLocal() { func initLocal() {
// consult $TZ to find the time zone to use. // consult $TZ to find the time zone to use.
// no $TZ means use the system default /etc/localtime. // no $TZ means use the system default /etc/localtime.
...@@ -56,14 +34,14 @@ func initLocal() { ...@@ -56,14 +34,14 @@ func initLocal() {
tz, ok := syscall.Getenv("TZ") tz, ok := syscall.Getenv("TZ")
switch { switch {
case !ok: case !ok:
z, err := loadZoneFile("", "/etc/localtime") z, err := loadLocation("localtime", []string{"/etc/"})
if err == nil { if err == nil {
localLoc = *z localLoc = *z
localLoc.name = "Local" localLoc.name = "Local"
return return
} }
case tz != "" && tz != "UTC": case tz != "" && tz != "UTC":
if z, err := loadLocation(tz); err == nil { if z, err := loadLocation(tz, zoneSources); err == nil {
localLoc = *z localLoc = *z
return return
} }
...@@ -72,19 +50,3 @@ func initLocal() { ...@@ -72,19 +50,3 @@ func initLocal() {
// Fall back to UTC. // Fall back to UTC.
localLoc.name = "UTC" localLoc.name = "UTC"
} }
func loadLocation(name string) (*Location, error) {
var firstErr error
for _, zoneDir := range zoneDirs {
if z, err := loadZoneFile(zoneDir, name); 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)
}
...@@ -11,6 +11,10 @@ import ( ...@@ -11,6 +11,10 @@ import (
"syscall" "syscall"
) )
var zoneSources = []string{
runtime.GOROOT() + "/lib/time/zoneinfo.zip",
}
// TODO(rsc): Fall back to copy of zoneinfo files. // TODO(rsc): Fall back to copy of zoneinfo files.
// BUG(brainman,rsc): On Windows, the operating system does not provide complete // BUG(brainman,rsc): On Windows, the operating system does not provide complete
...@@ -228,14 +232,6 @@ var aus = syscall.Timezoneinformation{ ...@@ -228,14 +232,6 @@ var aus = syscall.Timezoneinformation{
DaylightBias: -60, DaylightBias: -60,
} }
func initTestingZone() {
initLocalFromTZI(&usPacific)
}
func initAusTestingZone() {
initLocalFromTZI(&aus)
}
func initLocal() { func initLocal() {
var i syscall.Timezoneinformation var i syscall.Timezoneinformation
if _, err := syscall.GetTimeZoneInformation(&i); err != nil { if _, err := syscall.GetTimeZoneInformation(&i); err != nil {
...@@ -244,16 +240,3 @@ func initLocal() { ...@@ -244,16 +240,3 @@ func initLocal() {
} }
initLocalFromTZI(&i) initLocalFromTZI(&i)
} }
func loadLocation(name string) (*Location, error) {
z, err := loadZoneFile(runtime.GOROOT()+`\lib\time\zoneinfo.zip`, name)
if err != nil {
return nil, err
}
z.name = name
return z, nil
}
func forceZipFileForTesting(zipOnly bool) {
// We only use the zip file anyway.
}
...@@ -31,14 +31,14 @@ func testZoneAbbr(t *testing.T) { ...@@ -31,14 +31,14 @@ func testZoneAbbr(t *testing.T) {
} }
} }
func TestLocalZoneAbbr(t *testing.T) { func TestUSPacificZoneAbbr(t *testing.T) {
ResetLocalOnceForTest() // reset the Once to trigger the race ForceUSPacificFromTZIForTesting() // reset the Once to trigger the race
defer ForceUSPacificForTesting() defer ForceUSPacificForTesting()
testZoneAbbr(t) testZoneAbbr(t)
} }
func TestAusZoneAbbr(t *testing.T) { func TestAusZoneAbbr(t *testing.T) {
ForceAusForTesting() ForceAusFromTZIForTesting()
defer ForceUSPacificForTesting() defer ForceUSPacificForTesting()
testZoneAbbr(t) testZoneAbbr(t)
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment