]> Cypherpunks repositories - gostls13.git/commitdiff
time: use Go distribution zoneinfo if system copy not found
authorRuss Cox <rsc@golang.org>
Sun, 19 Feb 2012 02:02:41 +0000 (21:02 -0500)
committerRuss Cox <rsc@golang.org>
Sun, 19 Feb 2012 02:02:41 +0000 (21:02 -0500)
Fixes #2964.

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/5656101

src/pkg/time/zoneinfo.go
src/pkg/time/zoneinfo_plan9.go
src/pkg/time/zoneinfo_read.go [new file with mode: 0644]
src/pkg/time/zoneinfo_unix.go
src/pkg/time/zoneinfo_windows.go

index aca56e746af1870e8e65d0d54ae9994841029615..fa03f3225e6cd96c7af6dbdc2f4c926a2d8ab3c2 100644 (file)
@@ -4,7 +4,10 @@
 
 package time
 
-import "sync"
+import (
+       "sync"
+       "syscall"
+)
 
 // A Location maps time instants to the zone in use at that time.
 // Typically, the Location represents the collection of time offsets
@@ -168,10 +171,7 @@ func (l *Location) lookupOffset(offset int) (name string, isDST bool, ok bool) {
 // NOTE(rsc): Eventually we will need to accept the POSIX TZ environment
 // syntax too, but I don't feel like implementing it today.
 
-// NOTE(rsc): Using the IANA names below means ensuring we have access
-// to the database.  Probably we will ship the files in $GOROOT/lib/zoneinfo/
-// and only look there if there are no system files available (such as on Windows).
-// The files total 200 kB.
+var zoneinfo, _ = syscall.Getenv("ZONEINFO")
 
 // LoadLocation returns the Location with the given name.
 //
@@ -180,6 +180,12 @@ func (l *Location) lookupOffset(offset int) (name string, isDST bool, ok bool) {
 //
 // Otherwise, the name is taken to be a location name corresponding to a file
 // in the IANA Time Zone database, such as "America/New_York".
+//
+// The time zone database needed by LoadLocation may not be
+// present on all systems, especially non-Unix systems.
+// LoadLocation looks in the directory 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.
 func LoadLocation(name string) (*Location, error) {
        if name == "" || name == "UTC" {
                return UTC, nil
@@ -187,5 +193,11 @@ func LoadLocation(name string) (*Location, error) {
        if name == "Local" {
                return Local, nil
        }
+       if zoneinfo != "" {
+               if z, err := loadZoneFile(zoneinfo + "/" + name); err == nil {
+                       z.name = name
+                       return z, nil
+               }
+       }
        return loadLocation(name)
 }
index 9c052d42cd32376b2aa4dfdb58adbbb2e360558e..0fc2c25c0bafe43498e825b9f1c0de69354b55bd 100644 (file)
@@ -51,7 +51,7 @@ func fields(s string) []string {
        return a
 }
 
-func loadZoneData(s string) (l *Location, err error) {
+func loadZoneDataPlan9(s string) (l *Location, err error) {
        f := fields(s)
        if len(f) < 4 {
                if len(f) == 2 && f[0] == "GMT" {
@@ -112,33 +112,32 @@ func loadZoneData(s string) (l *Location, err error) {
        return l, nil
 }
 
-func loadZoneFile(name string) (*Location, error) {
+func loadZoneFilePlan9(name string) (*Location, error) {
        b, err := readFile(name)
        if err != nil {
                return nil, err
        }
-       return loadZoneData(string(b))
+       return loadZoneDataPlan9(string(b))
 }
 
 func initTestingZone() {
-       if z, err := loadZoneFile("/adm/timezone/US_Pacific"); err == nil {
-               localLoc = *z
-               return
+       z, err := loadLocation("America/Los_Angeles")
+       if err != nil {
+               panic("cannot load America/Los_Angeles for testing: " + err.Error())
        }
-
-       // Fall back to UTC.
-       localLoc.name = "UTC"
+       z.name = "Local"
+       localLoc = *z
 }
 
 func initLocal() {
        t, ok := syscall.Getenv("timezone")
        if ok {
-               if z, err := loadZoneData(t); err == nil {
+               if z, err := loadZoneDataPlan9(t); err == nil {
                        localLoc = *z
                        return
                }
        } else {
-               if z, err := loadZoneFile("/adm/timezone/local"); err == nil {
+               if z, err := loadZoneFilePlan9("/adm/timezone/local"); err == nil {
                        localLoc = *z
                        localLoc.name = "Local"
                        return
@@ -150,7 +149,8 @@ func initLocal() {
 }
 
 func loadLocation(name string) (*Location, error) {
-       if z, err := loadZoneFile("/adm/timezone/" + name); err == nil {
+       if z, err := loadZoneFile(runtime.GOROOT() + "/lib/time/zoneinfo/" + name); err == nil {
+               z.name = name
                return z, nil
        }
        return nil, errors.New("unknown time zone " + name)
diff --git a/src/pkg/time/zoneinfo_read.go b/src/pkg/time/zoneinfo_read.go
new file mode 100644 (file)
index 0000000..57eebe7
--- /dev/null
@@ -0,0 +1,203 @@
+// Copyright 2009 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 "zoneinfo" time zone file.
+// This is a fairly standard file format used on OS X, Linux, BSD, Sun, and others.
+// See tzfile(5), http://en.wikipedia.org/wiki/Zoneinfo,
+// and ftp://munnari.oz.au/pub/oldtz/
+
+package time
+
+import "errors"
+
+const (
+       headerSize = 4 + 16 + 4*7
+)
+
+// Simple I/O interface to binary blob of data.
+type data struct {
+       p     []byte
+       error bool
+}
+
+func (d *data) read(n int) []byte {
+       if len(d.p) < n {
+               d.p = nil
+               d.error = true
+               return nil
+       }
+       p := d.p[0:n]
+       d.p = d.p[n:]
+       return p
+}
+
+func (d *data) big4() (n uint32, ok bool) {
+       p := d.read(4)
+       if len(p) < 4 {
+               d.error = true
+               return 0, false
+       }
+       return uint32(p[0])<<24 | uint32(p[1])<<16 | uint32(p[2])<<8 | uint32(p[3]), true
+}
+
+func (d *data) byte() (n byte, ok bool) {
+       p := d.read(1)
+       if len(p) < 1 {
+               d.error = true
+               return 0, false
+       }
+       return p[0], true
+}
+
+// Make a string by stopping at the first NUL
+func byteString(p []byte) string {
+       for i := 0; i < len(p); i++ {
+               if p[i] == 0 {
+                       return string(p[0:i])
+               }
+       }
+       return string(p)
+}
+
+var badData = errors.New("malformed time zone information")
+
+func loadZoneData(bytes []byte) (l *Location, err error) {
+       d := data{bytes, false}
+
+       // 4-byte magic "TZif"
+       if magic := d.read(4); string(magic) != "TZif" {
+               return nil, badData
+       }
+
+       // 1-byte version, then 15 bytes of padding
+       var p []byte
+       if p = d.read(16); len(p) != 16 || p[0] != 0 && p[0] != '2' {
+               return nil, badData
+       }
+
+       // six big-endian 32-bit integers:
+       //      number of UTC/local indicators
+       //      number of standard/wall indicators
+       //      number of leap seconds
+       //      number of transition times
+       //      number of local time zones
+       //      number of characters of time zone abbrev strings
+       const (
+               NUTCLocal = iota
+               NStdWall
+               NLeap
+               NTime
+               NZone
+               NChar
+       )
+       var n [6]int
+       for i := 0; i < 6; i++ {
+               nn, ok := d.big4()
+               if !ok {
+                       return nil, badData
+               }
+               n[i] = int(nn)
+       }
+
+       // Transition times.
+       txtimes := data{d.read(n[NTime] * 4), false}
+
+       // Time zone indices for transition times.
+       txzones := d.read(n[NTime])
+
+       // Zone info structures
+       zonedata := data{d.read(n[NZone] * 6), false}
+
+       // Time zone abbreviations.
+       abbrev := d.read(n[NChar])
+
+       // Leap-second time pairs
+       d.read(n[NLeap] * 8)
+
+       // Whether tx times associated with local time types
+       // are specified as standard time or wall time.
+       isstd := d.read(n[NStdWall])
+
+       // Whether tx times associated with local time types
+       // are specified as UTC or local time.
+       isutc := d.read(n[NUTCLocal])
+
+       if d.error { // ran out of data
+               return nil, badData
+       }
+
+       // If version == 2, the entire file repeats, this time using
+       // 8-byte ints for txtimes and leap seconds.
+       // We won't need those until 2106.
+
+       // Now we can build up a useful data structure.
+       // First the zone information.
+       //      utcoff[4] isdst[1] nameindex[1]
+       zone := make([]zone, n[NZone])
+       for i := range zone {
+               var ok bool
+               var n uint32
+               if n, ok = zonedata.big4(); !ok {
+                       return nil, badData
+               }
+               zone[i].offset = int(n)
+               var b byte
+               if b, ok = zonedata.byte(); !ok {
+                       return nil, badData
+               }
+               zone[i].isDST = b != 0
+               if b, ok = zonedata.byte(); !ok || int(b) >= len(abbrev) {
+                       return nil, badData
+               }
+               zone[i].name = byteString(abbrev[b:])
+       }
+
+       // Now the transition time info.
+       tx := make([]zoneTrans, n[NTime])
+       for i := range tx {
+               var ok bool
+               var n uint32
+               if n, ok = txtimes.big4(); !ok {
+                       return nil, badData
+               }
+               tx[i].when = int64(int32(n))
+               if int(txzones[i]) >= len(zone) {
+                       return nil, badData
+               }
+               tx[i].index = txzones[i]
+               if i < len(isstd) {
+                       tx[i].isstd = isstd[i] != 0
+               }
+               if i < len(isutc) {
+                       tx[i].isutc = isutc[i] != 0
+               }
+       }
+
+       // Commited to succeed.
+       l = &Location{zone: zone, tx: tx}
+
+       // Fill in the cache with information about right now,
+       // since that will be the most common lookup.
+       sec, _ := now()
+       for i := range tx {
+               if tx[i].when <= sec && (i+1 == len(tx) || sec < tx[i+1].when) {
+                       l.cacheStart = tx[i].when
+                       l.cacheEnd = 1<<63 - 1
+                       if i+1 < len(tx) {
+                               l.cacheEnd = tx[i+1].when
+                       }
+                       l.cacheZone = &l.zone[tx[i].index]
+               }
+       }
+
+       return l, nil
+}
+
+func loadZoneFile(name string) (l *Location, err error) {
+       buf, err := readFile(name)
+       if err != nil {
+               return
+       }
+       return loadZoneData(buf)
+}
index 540b653c57d22e9977e38c291f3d29ca2c68a0d7..8d0f1133a0ef7e70e503d52dfe195dc67e4c5578 100644 (file)
@@ -13,203 +13,17 @@ package time
 
 import (
        "errors"
+       "runtime"
        "syscall"
 )
 
-const (
-       headerSize = 4 + 16 + 4*7
-)
-
-// Simple I/O interface to binary blob of data.
-type data struct {
-       p     []byte
-       error bool
-}
-
-func (d *data) read(n int) []byte {
-       if len(d.p) < n {
-               d.p = nil
-               d.error = true
-               return nil
-       }
-       p := d.p[0:n]
-       d.p = d.p[n:]
-       return p
-}
-
-func (d *data) big4() (n uint32, ok bool) {
-       p := d.read(4)
-       if len(p) < 4 {
-               d.error = true
-               return 0, false
-       }
-       return uint32(p[0])<<24 | uint32(p[1])<<16 | uint32(p[2])<<8 | uint32(p[3]), true
-}
-
-func (d *data) byte() (n byte, ok bool) {
-       p := d.read(1)
-       if len(p) < 1 {
-               d.error = true
-               return 0, false
-       }
-       return p[0], true
-}
-
-// Make a string by stopping at the first NUL
-func byteString(p []byte) string {
-       for i := 0; i < len(p); i++ {
-               if p[i] == 0 {
-                       return string(p[0:i])
-               }
-       }
-       return string(p)
-}
-
-var badData = errors.New("malformed time zone information")
-
-func loadZoneData(bytes []byte) (l *Location, err error) {
-       d := data{bytes, false}
-
-       // 4-byte magic "TZif"
-       if magic := d.read(4); string(magic) != "TZif" {
-               return nil, badData
-       }
-
-       // 1-byte version, then 15 bytes of padding
-       var p []byte
-       if p = d.read(16); len(p) != 16 || p[0] != 0 && p[0] != '2' {
-               return nil, badData
-       }
-
-       // six big-endian 32-bit integers:
-       //      number of UTC/local indicators
-       //      number of standard/wall indicators
-       //      number of leap seconds
-       //      number of transition times
-       //      number of local time zones
-       //      number of characters of time zone abbrev strings
-       const (
-               NUTCLocal = iota
-               NStdWall
-               NLeap
-               NTime
-               NZone
-               NChar
-       )
-       var n [6]int
-       for i := 0; i < 6; i++ {
-               nn, ok := d.big4()
-               if !ok {
-                       return nil, badData
-               }
-               n[i] = int(nn)
-       }
-
-       // Transition times.
-       txtimes := data{d.read(n[NTime] * 4), false}
-
-       // Time zone indices for transition times.
-       txzones := d.read(n[NTime])
-
-       // Zone info structures
-       zonedata := data{d.read(n[NZone] * 6), false}
-
-       // Time zone abbreviations.
-       abbrev := d.read(n[NChar])
-
-       // Leap-second time pairs
-       d.read(n[NLeap] * 8)
-
-       // Whether tx times associated with local time types
-       // are specified as standard time or wall time.
-       isstd := d.read(n[NStdWall])
-
-       // Whether tx times associated with local time types
-       // are specified as UTC or local time.
-       isutc := d.read(n[NUTCLocal])
-
-       if d.error { // ran out of data
-               return nil, badData
-       }
-
-       // If version == 2, the entire file repeats, this time using
-       // 8-byte ints for txtimes and leap seconds.
-       // We won't need those until 2106.
-
-       // Now we can build up a useful data structure.
-       // First the zone information.
-       //      utcoff[4] isdst[1] nameindex[1]
-       zone := make([]zone, n[NZone])
-       for i := range zone {
-               var ok bool
-               var n uint32
-               if n, ok = zonedata.big4(); !ok {
-                       return nil, badData
-               }
-               zone[i].offset = int(n)
-               var b byte
-               if b, ok = zonedata.byte(); !ok {
-                       return nil, badData
-               }
-               zone[i].isDST = b != 0
-               if b, ok = zonedata.byte(); !ok || int(b) >= len(abbrev) {
-                       return nil, badData
-               }
-               zone[i].name = byteString(abbrev[b:])
-       }
-
-       // Now the transition time info.
-       tx := make([]zoneTrans, n[NTime])
-       for i := range tx {
-               var ok bool
-               var n uint32
-               if n, ok = txtimes.big4(); !ok {
-                       return nil, badData
-               }
-               tx[i].when = int64(int32(n))
-               if int(txzones[i]) >= len(zone) {
-                       return nil, badData
-               }
-               tx[i].index = txzones[i]
-               if i < len(isstd) {
-                       tx[i].isstd = isstd[i] != 0
-               }
-               if i < len(isutc) {
-                       tx[i].isutc = isutc[i] != 0
-               }
-       }
-
-       // Commited to succeed.
-       l = &Location{zone: zone, tx: tx}
-
-       // Fill in the cache with information about right now,
-       // since that will be the most common lookup.
-       sec, _ := now()
-       for i := range tx {
-               if tx[i].when <= sec && (i+1 == len(tx) || sec < tx[i+1].when) {
-                       l.cacheStart = tx[i].when
-                       l.cacheEnd = 1<<63 - 1
-                       if i+1 < len(tx) {
-                               l.cacheEnd = tx[i+1].when
-                       }
-                       l.cacheZone = &l.zone[tx[i].index]
-               }
-       }
-
-       return l, nil
-}
-
-func loadZoneFile(name string) (l *Location, err error) {
-       buf, err := readFile(name)
+func initTestingZone() {
+       z, err := loadZoneFile(runtime.GOROOT() + "/lib/time/zoneinfo/" + "America/Los_Angeles")
        if err != nil {
-               return
+               panic("cannot load America/Los_Angeles for testing: " + err.Error())
        }
-       return loadZoneData(buf)
-}
-
-func initTestingZone() {
-       syscall.Setenv("TZ", "America/Los_Angeles")
-       initLocal()
+       z.name = "Local"
+       localLoc = *z
 }
 
 // Many systems use /usr/share/zoneinfo, Solaris 2 has
@@ -218,6 +32,7 @@ var zoneDirs = []string{
        "/usr/share/zoneinfo/",
        "/usr/share/lib/zoneinfo/",
        "/usr/lib/locale/TZ/",
+       runtime.GOROOT() + "/lib/time/zoneinfo/",
 }
 
 func initLocal() {
index beef4de92b077f70f4eca693303531874fa23d92..b1a0c22062cb2fd9526f5c91c7dbe646bab64dcc 100644 (file)
@@ -151,7 +151,10 @@ func initLocal() {
        initLocalFromTZI(&i)
 }
 
-// TODO(rsc): Implement.
 func loadLocation(name string) (*Location, error) {
+       if z, err := loadZoneFile(runtime.GOROOT() + `\lib\time\zoneinfo\` + name); err == nil {
+               z.name = name
+               return z, nil
+       }
        return nil, errors.New("unknown time zone " + name)
 }