mirror of
https://github.com/golang/go.git
synced 2025-05-22 16:09:37 +00:00
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/13854 Reviewed-by: Alex Brainman <alex.brainman@gmail.com> Run-TryBot: Alex Brainman <alex.brainman@gmail.com> TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
parent
32add8d7c8
commit
cba1528ceb
@ -12,6 +12,7 @@ import (
|
||||
"os"
|
||||
"syscall"
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
"internal/syscall/windows/registry"
|
||||
)
|
||||
@ -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 (
|
||||
_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 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 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 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
|
||||
|
@ -112,6 +112,53 @@ func (k Key) GetStringValue(name string) (val string, valtype uint32, err error)
|
||||
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
|
||||
// them with the values defined for the current user.
|
||||
// Use ExpandString to expand EXPAND_SZ strings.
|
||||
|
@ -16,6 +16,7 @@ var (
|
||||
procRegSetValueExW = modadvapi32.NewProc("RegSetValueExW")
|
||||
procRegEnumValueW = modadvapi32.NewProc("RegEnumValueW")
|
||||
procRegDeleteValueW = modadvapi32.NewProc("RegDeleteValueW")
|
||||
procRegLoadMUIStringW = modadvapi32.NewProc("RegLoadMUIStringW")
|
||||
procExpandEnvironmentStringsW = modkernel32.NewProc("ExpandEnvironmentStringsW")
|
||||
)
|
||||
|
||||
@ -59,6 +60,14 @@ func regDeleteValue(key syscall.Handle, name *uint16) (regerrno error) {
|
||||
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) {
|
||||
r0, _, e1 := syscall.Syscall(procExpandEnvironmentStringsW.Addr(), 3, uintptr(unsafe.Pointer(src)), uintptr(unsafe.Pointer(dst)), uintptr(size))
|
||||
n = uint32(r0)
|
||||
|
@ -20,8 +20,9 @@ import (
|
||||
// The implementation assumes that this year's rules for daylight savings
|
||||
// time apply to all previous and future years as well.
|
||||
|
||||
// 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.
|
||||
// matchZoneKey checks if stdname and dstname match the corresponding key
|
||||
// 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) {
|
||||
k, err := registry.OpenKey(zones, kname, registry.READ)
|
||||
if err != nil {
|
||||
@ -29,18 +30,27 @@ func matchZoneKey(zones registry.Key, kname string, stdname, dstname string) (ma
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
s, _, err := k.GetStringValue("Std")
|
||||
if err != nil {
|
||||
return false, err
|
||||
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 s != stdname {
|
||||
if err != nil { // Fallback to Std and Dlt
|
||||
if std, _, err = k.GetStringValue("Std"); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if dlt, _, err = k.GetStringValue("Dlt"); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
if std != stdname {
|
||||
return false, nil
|
||||
}
|
||||
s, _, err = k.GetStringValue("Dlt")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if s != dstname && dstname != stdname {
|
||||
if dlt != dstname && dstname != stdname {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
|
@ -42,14 +42,24 @@ func TestToEnglishName(t *testing.T) {
|
||||
t.Fatalf("cannot open CEST time zone information from registry: %s", err)
|
||||
}
|
||||
defer k.Close()
|
||||
std, _, err := k.GetStringValue("Std")
|
||||
if err != nil {
|
||||
t.Fatalf("cannot read CEST Std registry key: %s", err)
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
dlt, _, err := k.GetStringValue("Dlt")
|
||||
if err != nil {
|
||||
t.Fatalf("cannot read CEST Dlt registry key: %s", err)
|
||||
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)
|
||||
}
|
||||
if dlt, _, err = k.GetStringValue("Dlt"); err != nil {
|
||||
t.Fatalf("cannot read CEST Dlt registry key: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
name, err := ToEnglishName(std, dlt)
|
||||
if err != nil {
|
||||
t.Fatalf("toEnglishName failed: %s", err)
|
||||
|
Loading…
x
Reference in New Issue
Block a user