diff --git a/src/pkg/time/zoneinfo_windows.go b/src/pkg/time/zoneinfo_windows.go index 541327f056c..1e18ad295df 100644 --- a/src/pkg/time/zoneinfo_windows.go +++ b/src/pkg/time/zoneinfo_windows.go @@ -8,6 +8,7 @@ import ( "errors" "runtime" "syscall" + "unsafe" ) // TODO(rsc): Fall back to copy of zoneinfo files. @@ -17,6 +18,78 @@ import ( // The implementation assumes that this year's rules for daylight savings // time apply to all previous and future years as well. +// getKeyValue retrieves the string value kname associated with the open registry key kh. +func getKeyValue(kh syscall.Handle, kname string) (string, error) { + var buf [50]uint16 // buf needs to be large enough to fit zone descriptions + var typ uint32 + n := uint32(len(buf) * 2) // RegQueryValueEx's signature expects array of bytes, not uint16 + p, _ := syscall.UTF16PtrFromString(kname) + if err := syscall.RegQueryValueEx(kh, p, nil, &typ, (*byte)(unsafe.Pointer(&buf[0])), &n); err != nil { + return "", err + } + if typ != syscall.REG_SZ { // null terminated strings only + return "", errors.New("Key is not string") + } + return syscall.UTF16ToString(buf[:]), nil +} + +// matchZoneKey checks if stdname and dstname match the corresponding "Std" +// and "Dlt" key values in the kname key stored under the open registry key zones. +func matchZoneKey(zones syscall.Handle, kname string, stdname, dstname string) (matched bool, err2 error) { + var h syscall.Handle + p, _ := syscall.UTF16PtrFromString(kname) + if err := syscall.RegOpenKeyEx(zones, p, 0, syscall.KEY_READ, &h); err != nil { + return false, err + } + defer syscall.RegCloseKey(h) + + s, err := getKeyValue(h, "Std") + if err != nil { + return false, err + } + if s != stdname { + return false, nil + } + s, err = getKeyValue(h, "Dlt") + if err != nil { + return false, err + } + if s != dstname { + return false, nil + } + return true, nil +} + +// toEnglishName searches the registry for an English name of a time zone +// whose zone names are stdname and dstname and returns the English name. +func toEnglishName(stdname, dstname string) (string, error) { + var zones syscall.Handle + p, _ := syscall.UTF16PtrFromString(`SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones`) + if err := syscall.RegOpenKeyEx(syscall.HKEY_LOCAL_MACHINE, p, 0, syscall.KEY_READ, &zones); err != nil { + return "", err + } + defer syscall.RegCloseKey(zones) + + var count uint32 + if err := syscall.RegQueryInfoKey(zones, nil, nil, nil, &count, nil, nil, nil, nil, nil, nil, nil); err != nil { + return "", err + } + + var buf [50]uint16 // buf needs to be large enough to fit zone descriptions + for i := uint32(0); i < count; i++ { + n := uint32(len(buf)) + if syscall.RegEnumKeyEx(zones, i, &buf[0], &n, nil, nil, nil, nil) != nil { + continue + } + kname := syscall.UTF16ToString(buf[:]) + matched, err := matchZoneKey(zones, kname, stdname, dstname) + if err == nil && matched { + return kname, nil + } + } + return "", errors.New(`English name for time zone "` + stdname + `" not found in registry`) +} + // extractCAPS exracts capital letters from description desc. func extractCAPS(desc string) string { var short []rune @@ -33,8 +106,16 @@ 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[:]) + // Perhaps stdName is not English. Try to convert it. + englishName, err := toEnglishName(stdName, dstName) + if err == nil { + a, ok = abbrs[englishName] + if ok { + return a.std, a.dst + } + } + // fallback to using capital letters return extractCAPS(stdName), extractCAPS(dstName) } return a.std, a.dst