Commit cba1528c authored by Daniel Johansson's avatar Daniel Johansson Committed by Alex Brainman

time: handle localized time zone names

The existing implementation fails to determine the correct time zone
abbreviations when the display language is non-English. This change adds
support for localized time zone names (standard- and daylightname)
by using the function RegLoadMUIString.

Fixes #12015

Change-Id: Ic0dc89c50993af8f292b199c20bc5932903e7e87
Reviewed-on: https://go-review.googlesource.com/13854Reviewed-by: 's avatarAlex Brainman <alex.brainman@gmail.com>
Run-TryBot: Alex Brainman <alex.brainman@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
parent 32add8d7
...@@ -12,6 +12,7 @@ import ( ...@@ -12,6 +12,7 @@ import (
"os" "os"
"syscall" "syscall"
"testing" "testing"
"unsafe"
"internal/syscall/windows/registry" "internal/syscall/windows/registry"
) )
...@@ -676,3 +677,74 @@ func TestInvalidValues(t *testing.T) { ...@@ -676,3 +677,74 @@ func TestInvalidValues(t *testing.T) {
} }
} }
} }
func TestGetMUIStringValue(t *testing.T) {
if err := registry.LoadRegLoadMUIString(); err != nil {
t.Skip("regLoadMUIString not supported; skipping")
}
if err := procGetDynamicTimeZoneInformation.Find(); err != nil {
t.Skipf("%s not supported; skipping", procGetDynamicTimeZoneInformation.Name)
}
var dtzi DynamicTimezoneinformation
if _, err := GetDynamicTimeZoneInformation(&dtzi); err != nil {
t.Fatal(err)
}
tzKeyName := syscall.UTF16ToString(dtzi.TimeZoneKeyName[:])
timezoneK, err := registry.OpenKey(registry.LOCAL_MACHINE,
`SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\`+tzKeyName, registry.READ)
if err != nil {
t.Fatal(err)
}
defer timezoneK.Close()
var tests = []struct {
key registry.Key
name string
want string
}{
{key: timezoneK, name: "MUI_Std", want: syscall.UTF16ToString(dtzi.StandardName[:])},
{key: timezoneK, name: "MUI_Dlt", want: syscall.UTF16ToString(dtzi.DaylightName[:])},
}
for _, test := range tests {
got, err := test.key.GetMUIStringValue(test.name)
if err != nil {
t.Error("GetMUIStringValue:", err)
}
if got != test.want {
t.Errorf("GetMUIStringValue: %s: Got %q, want %q", test.name, got, test.want)
}
}
}
type DynamicTimezoneinformation struct {
Bias int32
StandardName [32]uint16
StandardDate syscall.Systemtime
StandardBias int32
DaylightName [32]uint16
DaylightDate syscall.Systemtime
DaylightBias int32
TimeZoneKeyName [128]uint16
DynamicDaylightTimeDisabled uint8
}
var (
kernel32DLL = syscall.NewLazyDLL("kernel32")
procGetDynamicTimeZoneInformation = kernel32DLL.NewProc("GetDynamicTimeZoneInformation")
)
func GetDynamicTimeZoneInformation(dtzi *DynamicTimezoneinformation) (rc uint32, err error) {
r0, _, e1 := syscall.Syscall(procGetDynamicTimeZoneInformation.Addr(), 1, uintptr(unsafe.Pointer(dtzi)), 0, 0)
rc = uint32(r0)
if rc == 0xffffffff {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
...@@ -19,10 +19,15 @@ const ( ...@@ -19,10 +19,15 @@ const (
_ERROR_NO_MORE_ITEMS syscall.Errno = 259 _ERROR_NO_MORE_ITEMS syscall.Errno = 259
) )
func LoadRegLoadMUIString() error {
return procRegLoadMUIStringW.Find()
}
//sys regCreateKeyEx(key syscall.Handle, subkey *uint16, reserved uint32, class *uint16, options uint32, desired uint32, sa *syscall.SecurityAttributes, result *syscall.Handle, disposition *uint32) (regerrno error) = advapi32.RegCreateKeyExW //sys regCreateKeyEx(key syscall.Handle, subkey *uint16, reserved uint32, class *uint16, options uint32, desired uint32, sa *syscall.SecurityAttributes, result *syscall.Handle, disposition *uint32) (regerrno error) = advapi32.RegCreateKeyExW
//sys regDeleteKey(key syscall.Handle, subkey *uint16) (regerrno error) = advapi32.RegDeleteKeyW //sys regDeleteKey(key syscall.Handle, subkey *uint16) (regerrno error) = advapi32.RegDeleteKeyW
//sys regSetValueEx(key syscall.Handle, valueName *uint16, reserved uint32, vtype uint32, buf *byte, bufsize uint32) (regerrno error) = advapi32.RegSetValueExW //sys regSetValueEx(key syscall.Handle, valueName *uint16, reserved uint32, vtype uint32, buf *byte, bufsize uint32) (regerrno error) = advapi32.RegSetValueExW
//sys regEnumValue(key syscall.Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, valtype *uint32, buf *byte, buflen *uint32) (regerrno error) = advapi32.RegEnumValueW //sys regEnumValue(key syscall.Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, valtype *uint32, buf *byte, buflen *uint32) (regerrno error) = advapi32.RegEnumValueW
//sys regDeleteValue(key syscall.Handle, name *uint16) (regerrno error) = advapi32.RegDeleteValueW //sys regDeleteValue(key syscall.Handle, name *uint16) (regerrno error) = advapi32.RegDeleteValueW
//sys regLoadMUIString(key syscall.Handle, name *uint16, buf *uint16, buflen uint32, buflenCopied *uint32, flags uint32, dir *uint16) (regerrno error) = advapi32.RegLoadMUIStringW
//sys expandEnvironmentStrings(src *uint16, dst *uint16, size uint32) (n uint32, err error) = kernel32.ExpandEnvironmentStringsW //sys expandEnvironmentStrings(src *uint16, dst *uint16, size uint32) (n uint32, err error) = kernel32.ExpandEnvironmentStringsW
...@@ -112,6 +112,53 @@ func (k Key) GetStringValue(name string) (val string, valtype uint32, err error) ...@@ -112,6 +112,53 @@ func (k Key) GetStringValue(name string) (val string, valtype uint32, err error)
return syscall.UTF16ToString(u), typ, nil return syscall.UTF16ToString(u), typ, nil
} }
// GetMUIStringValue retrieves the localized string value for
// the specified value name associated with an open key k.
// If the value name doesn't exist or the localized string value
// can't be resolved, GetMUIStringValue returns ErrNotExist.
// GetMUIStringValue panics if the system doesn't support
// regLoadMUIString; use LoadRegLoadMUIString to check if
// regLoadMUIString is supported before calling this function.
func (k Key) GetMUIStringValue(name string) (string, error) {
pname, err := syscall.UTF16PtrFromString(name)
if err != nil {
return "", err
}
buf := make([]uint16, 1024)
var buflen uint32
var pdir *uint16
err = regLoadMUIString(syscall.Handle(k), pname, &buf[0], uint32(len(buf)), &buflen, 0, pdir)
if err == syscall.ERROR_FILE_NOT_FOUND { // Try fallback path
var s string
s, err = ExpandString("%SystemRoot%\\system32\\")
if err != nil {
return "", err
}
pdir, err = syscall.UTF16PtrFromString(s)
if err != nil {
return "", err
}
err = regLoadMUIString(syscall.Handle(k), pname, &buf[0], uint32(len(buf)), &buflen, 0, pdir)
}
for err == syscall.ERROR_MORE_DATA { // Grow buffer if needed
if buflen <= uint32(len(buf)) {
break // Buffer not growing, assume race; break
}
buf = make([]uint16, buflen)
err = regLoadMUIString(syscall.Handle(k), pname, &buf[0], uint32(len(buf)), &buflen, 0, pdir)
}
if err != nil {
return "", err
}
return syscall.UTF16ToString(buf), nil
}
// ExpandString expands environment-variable strings and replaces // ExpandString expands environment-variable strings and replaces
// them with the values defined for the current user. // them with the values defined for the current user.
// Use ExpandString to expand EXPAND_SZ strings. // Use ExpandString to expand EXPAND_SZ strings.
......
...@@ -16,6 +16,7 @@ var ( ...@@ -16,6 +16,7 @@ var (
procRegSetValueExW = modadvapi32.NewProc("RegSetValueExW") procRegSetValueExW = modadvapi32.NewProc("RegSetValueExW")
procRegEnumValueW = modadvapi32.NewProc("RegEnumValueW") procRegEnumValueW = modadvapi32.NewProc("RegEnumValueW")
procRegDeleteValueW = modadvapi32.NewProc("RegDeleteValueW") procRegDeleteValueW = modadvapi32.NewProc("RegDeleteValueW")
procRegLoadMUIStringW = modadvapi32.NewProc("RegLoadMUIStringW")
procExpandEnvironmentStringsW = modkernel32.NewProc("ExpandEnvironmentStringsW") procExpandEnvironmentStringsW = modkernel32.NewProc("ExpandEnvironmentStringsW")
) )
...@@ -59,6 +60,14 @@ func regDeleteValue(key syscall.Handle, name *uint16) (regerrno error) { ...@@ -59,6 +60,14 @@ func regDeleteValue(key syscall.Handle, name *uint16) (regerrno error) {
return return
} }
func regLoadMUIString(key syscall.Handle, name *uint16, buf *uint16, buflen uint32, buflenCopied *uint32, flags uint32, dir *uint16) (regerrno error) {
r0, _, _ := syscall.Syscall9(procRegLoadMUIStringW.Addr(), 7, uintptr(key), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buf)), uintptr(buflen), uintptr(unsafe.Pointer(buflenCopied)), uintptr(flags), uintptr(unsafe.Pointer(dir)), 0, 0)
if r0 != 0 {
regerrno = syscall.Errno(r0)
}
return
}
func expandEnvironmentStrings(src *uint16, dst *uint16, size uint32) (n uint32, err error) { func expandEnvironmentStrings(src *uint16, dst *uint16, size uint32) (n uint32, err error) {
r0, _, e1 := syscall.Syscall(procExpandEnvironmentStringsW.Addr(), 3, uintptr(unsafe.Pointer(src)), uintptr(unsafe.Pointer(dst)), uintptr(size)) r0, _, e1 := syscall.Syscall(procExpandEnvironmentStringsW.Addr(), 3, uintptr(unsafe.Pointer(src)), uintptr(unsafe.Pointer(dst)), uintptr(size))
n = uint32(r0) n = uint32(r0)
......
...@@ -20,8 +20,9 @@ import ( ...@@ -20,8 +20,9 @@ import (
// The implementation assumes that this year's rules for daylight savings // The implementation assumes that this year's rules for daylight savings
// time apply to all previous and future years as well. // time apply to all previous and future years as well.
// matchZoneKey checks if stdname and dstname match the corresponding "Std" // matchZoneKey checks if stdname and dstname match the corresponding key
// and "Dlt" key values in the kname key stored under the open registry key zones. // values "MUI_Std" and MUI_Dlt" or "Std" and "Dlt" (the latter down-level
// from Vista) in the kname key stored under the open registry key zones.
func matchZoneKey(zones registry.Key, kname string, stdname, dstname string) (matched bool, err2 error) { func matchZoneKey(zones registry.Key, kname string, stdname, dstname string) (matched bool, err2 error) {
k, err := registry.OpenKey(zones, kname, registry.READ) k, err := registry.OpenKey(zones, kname, registry.READ)
if err != nil { if err != nil {
...@@ -29,18 +30,27 @@ func matchZoneKey(zones registry.Key, kname string, stdname, dstname string) (ma ...@@ -29,18 +30,27 @@ func matchZoneKey(zones registry.Key, kname string, stdname, dstname string) (ma
} }
defer k.Close() defer k.Close()
s, _, err := k.GetStringValue("Std") var std, dlt string
if err != nil { if err = registry.LoadRegLoadMUIString(); err == nil {
return false, err // Try MUI_Std and MUI_Dlt first, fallback to Std and Dlt if *any* error occurs
std, err = k.GetMUIStringValue("MUI_Std")
if err == nil {
dlt, err = k.GetMUIStringValue("MUI_Dlt")
} }
if s != stdname {
return false, nil
} }
s, _, err = k.GetStringValue("Dlt") if err != nil { // Fallback to Std and Dlt
if err != nil { if std, _, err = k.GetStringValue("Std"); err != nil {
return false, err return false, err
} }
if s != dstname && dstname != stdname { if dlt, _, err = k.GetStringValue("Dlt"); err != nil {
return false, err
}
}
if std != stdname {
return false, nil
}
if dlt != dstname && dstname != stdname {
return false, nil return false, nil
} }
return true, nil return true, nil
......
...@@ -42,14 +42,24 @@ func TestToEnglishName(t *testing.T) { ...@@ -42,14 +42,24 @@ func TestToEnglishName(t *testing.T) {
t.Fatalf("cannot open CEST time zone information from registry: %s", err) t.Fatalf("cannot open CEST time zone information from registry: %s", err)
} }
defer k.Close() defer k.Close()
std, _, err := k.GetStringValue("Std")
if err != nil { var std, dlt string
if err = registry.LoadRegLoadMUIString(); err == nil {
// Try MUI_Std and MUI_Dlt first, fallback to Std and Dlt if *any* error occurs
std, err = k.GetMUIStringValue("MUI_Std")
if err == nil {
dlt, err = k.GetMUIStringValue("MUI_Dlt")
}
}
if err != nil { // Fallback to Std and Dlt
if std, _, err = k.GetStringValue("Std"); err != nil {
t.Fatalf("cannot read CEST Std registry key: %s", err) t.Fatalf("cannot read CEST Std registry key: %s", err)
} }
dlt, _, err := k.GetStringValue("Dlt") if dlt, _, err = k.GetStringValue("Dlt"); err != nil {
if err != nil {
t.Fatalf("cannot read CEST Dlt registry key: %s", err) t.Fatalf("cannot read CEST Dlt registry key: %s", err)
} }
}
name, err := ToEnglishName(std, dlt) name, err := ToEnglishName(std, dlt)
if err != nil { if err != nil {
t.Fatalf("toEnglishName failed: %s", err) t.Fatalf("toEnglishName failed: %s", err)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment