]> Cypherpunks repositories - gostls13.git/commitdiff
time: provide timezone abbreviations on windows
authorAlex Brainman <alex.brainman@gmail.com>
Thu, 6 Jun 2013 06:30:25 +0000 (16:30 +1000)
committerAlex Brainman <alex.brainman@gmail.com>
Thu, 6 Jun 2013 06:30:25 +0000 (16:30 +1000)
Use http://unicode.org/cldr/data/common/supplemental/windowsZones.xml
to generate the map.

Fixes #4838.

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/9997043

src/pkg/time/Makefile [new file with mode: 0644]
src/pkg/time/export_windows_test.go [new file with mode: 0644]
src/pkg/time/genzabbrs.go [new file with mode: 0644]
src/pkg/time/zoneinfo_abbrs_windows.go [new file with mode: 0644]
src/pkg/time/zoneinfo_windows.go
src/pkg/time/zoneinfo_windows_test.go [new file with mode: 0644]

diff --git a/src/pkg/time/Makefile b/src/pkg/time/Makefile
new file mode 100644 (file)
index 0000000..cba58e4
--- /dev/null
@@ -0,0 +1,9 @@
+# Copyright 2013 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.
+
+genzabbrs: genzabbrs.go
+       go build genzabbrs.go
+
+windows: genzabbrs
+       ./genzabbrs | gofmt >zoneinfo_abbrs_windows.go
diff --git a/src/pkg/time/export_windows_test.go b/src/pkg/time/export_windows_test.go
new file mode 100644 (file)
index 0000000..7e689b8
--- /dev/null
@@ -0,0 +1,10 @@
+// Copyright 2013 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.
+
+package time
+
+func ForceAusForTesting() {
+       ResetLocalOnceForTest()
+       localOnce.Do(initAusTestingZone)
+}
diff --git a/src/pkg/time/genzabbrs.go b/src/pkg/time/genzabbrs.go
new file mode 100644 (file)
index 0000000..7c637cb
--- /dev/null
@@ -0,0 +1,145 @@
+// Copyright 2013 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.
+
+// +build ignore
+
+//
+// usage:
+//
+// go run genzabbrs.go | gofmt > $GOROOT/src/pkg/time/zoneinfo_abbrs_windows.go
+//
+
+package main
+
+import (
+       "encoding/xml"
+       "io/ioutil"
+       "log"
+       "net/http"
+       "os"
+       "sort"
+       "text/template"
+       "time"
+)
+
+// getAbbrs finds timezone abbreviations (standard and daylight saving time)
+// for location l.
+func getAbbrs(l *time.Location) (st, dt string) {
+       t := time.Date(time.Now().Year(), 0, 0, 0, 0, 0, 0, l)
+       abbr1, off1 := t.Zone()
+       for i := 0; i < 12; i++ {
+               t = t.AddDate(0, 1, 0)
+               abbr2, off2 := t.Zone()
+               if abbr1 != abbr2 {
+                       if off2-off1 < 0 { // southern hemisphere
+                               abbr1, abbr2 = abbr2, abbr1
+                       }
+                       return abbr1, abbr2
+               }
+       }
+       return abbr1, abbr1
+}
+
+type zone struct {
+       WinName  string
+       UnixName string
+       StTime   string
+       DSTime   string
+}
+
+type zones []*zone
+
+func (zs zones) Len() int           { return len(zs) }
+func (zs zones) Swap(i, j int)      { zs[i], zs[j] = zs[j], zs[i] }
+func (zs zones) Less(i, j int) bool { return zs[i].UnixName < zs[j].UnixName }
+
+const wzURL = "http://unicode.org/cldr/data/common/supplemental/windowsZones.xml"
+
+type MapZone struct {
+       Other     string `xml:"other,attr"`
+       Territory string `xml:"territory,attr"`
+       Type      string `xml:"type,attr"`
+}
+
+type SupplementalData struct {
+       Zones []MapZone `xml:"windowsZones>mapTimezones>mapZone"`
+}
+
+func readWindowsZones() (zones, error) {
+       r, err := http.Get(wzURL)
+       if err != nil {
+               return nil, err
+       }
+       defer r.Body.Close()
+
+       data, err := ioutil.ReadAll(r.Body)
+       if err != nil {
+               return nil, err
+       }
+
+       var sd SupplementalData
+       err = xml.Unmarshal(data, &sd)
+       if err != nil {
+               return nil, err
+       }
+       zs := make(zones, 0)
+       for _, z := range sd.Zones {
+               if z.Territory != "001" {
+                       // to avoid dups. I don't know why.
+                       continue
+               }
+               l, err := time.LoadLocation(z.Type)
+               if err != nil {
+                       return nil, err
+               }
+               st, dt := getAbbrs(l)
+               zs = append(zs, &zone{
+                       WinName:  z.Other,
+                       UnixName: z.Type,
+                       StTime:   st,
+                       DSTime:   dt,
+               })
+       }
+       return zs, nil
+}
+
+func main() {
+       zs, err := readWindowsZones()
+       if err != nil {
+               log.Fatal(err)
+       }
+       sort.Sort(zs)
+       var v = struct {
+               URL string
+               Zs  zones
+       }{
+               wzURL,
+               zs,
+       }
+       err = template.Must(template.New("prog").Parse(prog)).Execute(os.Stdout, v)
+       if err != nil {
+               log.Fatal(err)
+       }
+}
+
+const prog = `
+// Copyright 2013 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.
+
+// generated by genzabbrs.go from
+// {{.URL}}
+
+package time
+
+type abbr struct {
+       std string
+       dst string
+}
+
+var abbrs = map[string]abbr{
+{{range .Zs}}  "{{.WinName}}": {"{{.StTime}}", "{{.DSTime}}"}, // {{.UnixName}}
+{{end}}}
+
+`
diff --git a/src/pkg/time/zoneinfo_abbrs_windows.go b/src/pkg/time/zoneinfo_abbrs_windows.go
new file mode 100644 (file)
index 0000000..8033437
--- /dev/null
@@ -0,0 +1,115 @@
+// Copyright 2013 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.
+
+// generated by genzabbrs.go from
+// http://unicode.org/cldr/data/common/supplemental/windowsZones.xml
+
+package time
+
+type abbr struct {
+       std string
+       dst string
+}
+
+var abbrs = map[string]abbr{
+       "Egypt Standard Time":             {"EET", "EET"},    // Africa/Cairo
+       "Morocco Standard Time":           {"WET", "WEST"},   // Africa/Casablanca
+       "South Africa Standard Time":      {"SAST", "SAST"},  // Africa/Johannesburg
+       "W. Central Africa Standard Time": {"WAT", "WAT"},    // Africa/Lagos
+       "E. Africa Standard Time":         {"EAT", "EAT"},    // Africa/Nairobi
+       "Namibia Standard Time":           {"WAT", "WAST"},   // Africa/Windhoek
+       "Alaskan Standard Time":           {"AKST", "AKDT"},  // America/Anchorage
+       "Paraguay Standard Time":          {"PYT", "PYST"},   // America/Asuncion
+       "Bahia Standard Time":             {"BRT", "BRST"},   // America/Bahia
+       "SA Pacific Standard Time":        {"COT", "COT"},    // America/Bogota
+       "Argentina Standard Time":         {"ART", "ART"},    // America/Buenos_Aires
+       "Venezuela Standard Time":         {"VET", "VET"},    // America/Caracas
+       "SA Eastern Standard Time":        {"GFT", "GFT"},    // America/Cayenne
+       "Central Standard Time":           {"CST", "CDT"},    // America/Chicago
+       "Mountain Standard Time (Mexico)": {"MST", "MDT"},    // America/Chihuahua
+       "Central Brazilian Standard Time": {"AMT", "AMST"},   // America/Cuiaba
+       "Mountain Standard Time":          {"MST", "MDT"},    // America/Denver
+       "Greenland Standard Time":         {"WGT", "WGST"},   // America/Godthab
+       "Central America Standard Time":   {"CST", "CST"},    // America/Guatemala
+       "Atlantic Standard Time":          {"AST", "ADT"},    // America/Halifax
+       "US Eastern Standard Time":        {"EST", "EDT"},    // America/Indianapolis
+       "SA Western Standard Time":        {"BOT", "BOT"},    // America/La_Paz
+       "Pacific Standard Time":           {"PST", "PDT"},    // America/Los_Angeles
+       "Central Standard Time (Mexico)":  {"CST", "CDT"},    // America/Mexico_City
+       "Montevideo Standard Time":        {"UYT", "UYST"},   // America/Montevideo
+       "Eastern Standard Time":           {"EST", "EDT"},    // America/New_York
+       "US Mountain Standard Time":       {"MST", "MST"},    // America/Phoenix
+       "Canada Central Standard Time":    {"CST", "CST"},    // America/Regina
+       "Pacific Standard Time (Mexico)":  {"PST", "PDT"},    // America/Santa_Isabel
+       "Pacific SA Standard Time":        {"CLT", "CLST"},   // America/Santiago
+       "E. South America Standard Time":  {"BRT", "BRST"},   // America/Sao_Paulo
+       "Newfoundland Standard Time":      {"NST", "NDT"},    // America/St_Johns
+       "Central Asia Standard Time":      {"ALMT", "ALMT"},  // Asia/Almaty
+       "Jordan Standard Time":            {"EET", "EEST"},   // Asia/Amman
+       "Arabic Standard Time":            {"AST", "AST"},    // Asia/Baghdad
+       "Azerbaijan Standard Time":        {"AZT", "AZST"},   // Asia/Baku
+       "SE Asia Standard Time":           {"ICT", "ICT"},    // Asia/Bangkok
+       "Middle East Standard Time":       {"EET", "EEST"},   // Asia/Beirut
+       "India Standard Time":             {"IST", "IST"},    // Asia/Calcutta
+       "Sri Lanka Standard Time":         {"IST", "IST"},    // Asia/Colombo
+       "Syria Standard Time":             {"EET", "EEST"},   // Asia/Damascus
+       "Bangladesh Standard Time":        {"BDT", "BDT"},    // Asia/Dhaka
+       "Arabian Standard Time":           {"GST", "GST"},    // Asia/Dubai
+       "North Asia East Standard Time":   {"IRKT", "IRKT"},  // Asia/Irkutsk
+       "Israel Standard Time":            {"IST", "IDT"},    // Asia/Jerusalem
+       "Afghanistan Standard Time":       {"AFT", "AFT"},    // Asia/Kabul
+       "Pakistan Standard Time":          {"PKT", "PKT"},    // Asia/Karachi
+       "Nepal Standard Time":             {"NPT", "NPT"},    // Asia/Katmandu
+       "North Asia Standard Time":        {"KRAT", "KRAT"},  // Asia/Krasnoyarsk
+       "Magadan Standard Time":           {"MAGT", "MAGT"},  // Asia/Magadan
+       "E. Europe Standard Time":         {"EET", "EEST"},   // Asia/Nicosia
+       "N. Central Asia Standard Time":   {"NOVT", "NOVT"},  // Asia/Novosibirsk
+       "Myanmar Standard Time":           {"MMT", "MMT"},    // Asia/Rangoon
+       "Arab Standard Time":              {"AST", "AST"},    // Asia/Riyadh
+       "Korea Standard Time":             {"KST", "KST"},    // Asia/Seoul
+       "China Standard Time":             {"CST", "CST"},    // Asia/Shanghai
+       "Singapore Standard Time":         {"SGT", "SGT"},    // Asia/Singapore
+       "Taipei Standard Time":            {"CST", "CST"},    // Asia/Taipei
+       "West Asia Standard Time":         {"UZT", "UZT"},    // Asia/Tashkent
+       "Georgian Standard Time":          {"GET", "GET"},    // Asia/Tbilisi
+       "Iran Standard Time":              {"IRST", "IRDT"},  // Asia/Tehran
+       "Tokyo Standard Time":             {"JST", "JST"},    // Asia/Tokyo
+       "Ulaanbaatar Standard Time":       {"ULAT", "ULAT"},  // Asia/Ulaanbaatar
+       "Vladivostok Standard Time":       {"VLAT", "VLAT"},  // Asia/Vladivostok
+       "Yakutsk Standard Time":           {"YAKT", "YAKT"},  // Asia/Yakutsk
+       "Ekaterinburg Standard Time":      {"YEKT", "YEKT"},  // Asia/Yekaterinburg
+       "Caucasus Standard Time":          {"AMT", "AMT"},    // Asia/Yerevan
+       "Azores Standard Time":            {"AZOT", "AZOST"}, // Atlantic/Azores
+       "Cape Verde Standard Time":        {"CVT", "CVT"},    // Atlantic/Cape_Verde
+       "Greenwich Standard Time":         {"GMT", "GMT"},    // Atlantic/Reykjavik
+       "Cen. Australia Standard Time":    {"CST", "CST"},    // Australia/Adelaide
+       "E. Australia Standard Time":      {"EST", "EST"},    // Australia/Brisbane
+       "AUS Central Standard Time":       {"CST", "CST"},    // Australia/Darwin
+       "Tasmania Standard Time":          {"EST", "EST"},    // Australia/Hobart
+       "W. Australia Standard Time":      {"WST", "WST"},    // Australia/Perth
+       "AUS Eastern Standard Time":       {"EST", "EST"},    // Australia/Sydney
+       "UTC":                            {"GMT", "GMT"},       // Etc/GMT
+       "UTC-11":                         {"GMT+11", "GMT+11"}, // Etc/GMT+11
+       "Dateline Standard Time":         {"GMT+12", "GMT+12"}, // Etc/GMT+12
+       "UTC-02":                         {"GMT+2", "GMT+2"},   // Etc/GMT+2
+       "UTC+12":                         {"GMT-12", "GMT-12"}, // Etc/GMT-12
+       "W. Europe Standard Time":        {"CET", "CEST"},      // Europe/Berlin
+       "GTB Standard Time":              {"EET", "EEST"},      // Europe/Bucharest
+       "Central Europe Standard Time":   {"CET", "CEST"},      // Europe/Budapest
+       "Turkey Standard Time":           {"EET", "EEST"},      // Europe/Istanbul
+       "Kaliningrad Standard Time":      {"FET", "FET"},       // Europe/Kaliningrad
+       "FLE Standard Time":              {"EET", "EEST"},      // Europe/Kiev
+       "GMT Standard Time":              {"GMT", "BST"},       // Europe/London
+       "Russian Standard Time":          {"MSK", "MSK"},       // Europe/Moscow
+       "Romance Standard Time":          {"CET", "CEST"},      // Europe/Paris
+       "Central European Standard Time": {"CET", "CEST"},      // Europe/Warsaw
+       "Mauritius Standard Time":        {"MUT", "MUT"},       // Indian/Mauritius
+       "Samoa Standard Time":            {"WST", "WST"},       // Pacific/Apia
+       "New Zealand Standard Time":      {"NZST", "NZDT"},     // Pacific/Auckland
+       "Fiji Standard Time":             {"FJT", "FJT"},       // Pacific/Fiji
+       "Central Pacific Standard Time":  {"SBT", "SBT"},       // Pacific/Guadalcanal
+       "Hawaiian Standard Time":         {"HST", "HST"},       // Pacific/Honolulu
+       "West Pacific Standard Time":     {"PGT", "PGT"},       // Pacific/Port_Moresby
+       "Tonga Standard Time":            {"TOT", "TOT"},       // Pacific/Tongatapu
+}
index a8d3dcbfeb1b7baba0640440d213a7d1dd7630d0..541327f056c3cb04a834444c03e12f99dadfe8d8 100644 (file)
@@ -16,21 +16,11 @@ import (
 // time zone information.
 // The implementation assumes that this year's rules for daylight savings
 // time apply to all previous and future years as well.
-// Also, time zone abbreviations are unavailable.  The implementation constructs
-// them using the capital letters from a longer time zone description.
-
-// abbrev returns the abbreviation to use for the given zone name.
-func abbrev(name []uint16) string {
-       // name is 'Pacific Standard Time' but we want 'PST'.
-       // Extract just capital letters.  It's not perfect but the
-       // information we need is not available from the kernel.
-       // Because time zone abbreviations are not unique,
-       // Windows refuses to expose them.
-       //
-       // http://social.msdn.microsoft.com/Forums/eu/vclanguage/thread/a87e1d25-fb71-4fe0-ae9c-a9578c9753eb
-       // http://stackoverflow.com/questions/4195948/windows-time-zone-abbreviations-in-asp-net
+
+// extractCAPS exracts capital letters from description desc.
+func extractCAPS(desc string) string {
        var short []rune
-       for _, c := range name {
+       for _, c := range desc {
                if 'A' <= c && c <= 'Z' {
                        short = append(short, rune(c))
                }
@@ -38,6 +28,18 @@ func abbrev(name []uint16) string {
        return string(short)
 }
 
+// abbrev returns the abbreviations to use for the given zone z.
+func abbrev(z *syscall.Timezoneinformation) (std, dst string) {
+       stdName := syscall.UTF16ToString(z.StandardName[:])
+       a, ok := abbrs[stdName]
+       if !ok {
+               // fallback to using capital letters
+               dstName := syscall.UTF16ToString(z.DaylightName[:])
+               return extractCAPS(stdName), extractCAPS(dstName)
+       }
+       return a.std, a.dst
+}
+
 // pseudoUnix returns the pseudo-Unix time (seconds since Jan 1 1970 *LOCAL TIME*)
 // denoted by the system date+time d in the given year.
 // It is up to the caller to convert this local time into a UTC-based time.
@@ -75,8 +77,10 @@ func initLocalFromTZI(i *syscall.Timezoneinformation) {
        }
        l.zone = make([]zone, nzone)
 
+       stdname, dstname := abbrev(i)
+
        std := &l.zone[0]
-       std.name = abbrev(i.StandardName[0:])
+       std.name = stdname
        if nzone == 1 {
                // No daylight savings.
                std.offset = -int(i.Bias) * 60
@@ -95,7 +99,7 @@ func initLocalFromTZI(i *syscall.Timezoneinformation) {
        std.offset = -int(i.Bias+i.StandardBias) * 60
 
        dst := &l.zone[1]
-       dst.name = abbrev(i.DaylightName[0:])
+       dst.name = dstname
        dst.offset = -int(i.Bias+i.DaylightBias) * 60
        dst.isDST = true
 
@@ -142,10 +146,27 @@ var usPacific = syscall.Timezoneinformation{
        DaylightBias: -60,
 }
 
+var aus = syscall.Timezoneinformation{
+       Bias: -10 * 60,
+       StandardName: [32]uint16{
+               'A', 'U', 'S', ' ', 'E', 'a', 's', 't', 'e', 'r', 'n', ' ', 'S', 't', 'a', 'n', 'd', 'a', 'r', 'd', ' ', 'T', 'i', 'm', 'e',
+       },
+       StandardDate: syscall.Systemtime{Month: 4, Day: 1, Hour: 3},
+       DaylightName: [32]uint16{
+               'A', 'U', 'S', ' ', 'E', 'a', 's', 't', 'e', 'r', 'n', ' ', 'D', 'a', 'y', 'l', 'i', 'g', 'h', 't', ' ', 'T', 'i', 'm', 'e',
+       },
+       DaylightDate: syscall.Systemtime{Month: 10, Day: 1, Hour: 2},
+       DaylightBias: -60,
+}
+
 func initTestingZone() {
        initLocalFromTZI(&usPacific)
 }
 
+func initAusTestingZone() {
+       initLocalFromTZI(&aus)
+}
+
 func initLocal() {
        var i syscall.Timezoneinformation
        if _, err := syscall.GetTimeZoneInformation(&i); err != nil {
diff --git a/src/pkg/time/zoneinfo_windows_test.go b/src/pkg/time/zoneinfo_windows_test.go
new file mode 100644 (file)
index 0000000..9db81b7
--- /dev/null
@@ -0,0 +1,35 @@
+// Copyright 2013 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.
+
+package time_test
+
+import (
+       "testing"
+       . "time"
+)
+
+func testZoneAbbr(t *testing.T) {
+       t1 := Now()
+       // discard nsec
+       t1 = Date(t1.Year(), t1.Month(), t1.Day(), t1.Hour(), t1.Minute(), t1.Second(), 0, t1.Location())
+       t2, err := Parse(RFC1123, t1.Format(RFC1123))
+       if err != nil {
+               t.Fatalf("Parse failed: %v", err)
+       }
+       if t1 != t2 {
+               t.Fatalf("t1 (%v) is not equal to t2 (%v)", t1, t2)
+       }
+}
+
+func TestLocalZoneAbbr(t *testing.T) {
+       ResetLocalOnceForTest() // reset the Once to trigger the race
+       defer ForceUSPacificForTesting()
+       testZoneAbbr(t)
+}
+
+func TestAusZoneAbbr(t *testing.T) {
+       ForceAusForTesting()
+       defer ForceUSPacificForTesting()
+       testZoneAbbr(t)
+}