From: Alex Brainman Date: Thu, 6 Jun 2013 06:30:25 +0000 (+1000) Subject: time: provide timezone abbreviations on windows X-Git-Tag: go1.2rc2~1311 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=07ea243d50e5e4c27e0212d45fa4e80d649d179f;p=gostls13.git time: provide timezone abbreviations on windows 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 --- diff --git a/src/pkg/time/Makefile b/src/pkg/time/Makefile new file mode 100644 index 0000000000..cba58e4e03 --- /dev/null +++ b/src/pkg/time/Makefile @@ -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 index 0000000000..7e689b829f --- /dev/null +++ b/src/pkg/time/export_windows_test.go @@ -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 index 0000000000..7c637cb43a --- /dev/null +++ b/src/pkg/time/genzabbrs.go @@ -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 index 0000000000..80334371fe --- /dev/null +++ b/src/pkg/time/zoneinfo_abbrs_windows.go @@ -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 +} diff --git a/src/pkg/time/zoneinfo_windows.go b/src/pkg/time/zoneinfo_windows.go index a8d3dcbfeb..541327f056 100644 --- a/src/pkg/time/zoneinfo_windows.go +++ b/src/pkg/time/zoneinfo_windows.go @@ -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 index 0000000000..9db81b7cfd --- /dev/null +++ b/src/pkg/time/zoneinfo_windows_test.go @@ -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) +}