1
0
mirror of https://github.com/golang/go synced 2024-11-22 15:04:52 -07:00

time: find correct zone abbreviations even on non-English windows systems

Fixes #5783

R=golang-dev, r
CC=golang-dev
https://golang.org/cl/10956043
This commit is contained in:
Alex Brainman 2013-07-10 15:34:24 +10:00
parent 8268eadb9e
commit 231dfd9049

View File

@ -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