diff --git a/README.chromium b/README.chromium index 8450286b..0e5e561e 100644 --- a/README.chromium +++ b/README.chromium @@ -289,7 +289,17 @@ D. Local Modifications https://unicode-org.atlassian.net/browse/ICU-20140 - fix: https://github.com/unicode-org/icu/pull/136 -15. Align memory buffer used in UnicodeSet on ARM. +15. Cherry pick an upstream patch for Windows timezone detection + + - patches/wintz_detection.patch + - upstream bugs: + https://unicode-org.atlassian.net/browse/ICU-13842 + https://unicode-org.atlassian.net/browse/ICU-13827 + - fixes: + https://github.com/unicode-org/icu/pull/55 + https://github.com/unicode-org/icu/pull/129 + +16. Align memory buffer used in UnicodeSet on ARM. - patches/arm_align.patch - upstream bug: diff --git a/patches/wintz_detection.patch b/patches/wintz_detection.patch new file mode 100644 index 00000000..91c6a0c3 --- /dev/null +++ b/patches/wintz_detection.patch @@ -0,0 +1,537 @@ +diff --git a/source/common/putil.cpp b/source/common/putil.cpp +index 2d2de524..229656d6 100644 +--- a/source/common/putil.cpp ++++ b/source/common/putil.cpp +@@ -102,9 +102,8 @@ + # define NOMCX + # include + # include "unicode/uloc.h" +-#if U_PLATFORM_HAS_WINUWP_API == 0 + # include "wintz.h" +-#else // U_PLATFORM_HAS_WINUWP_API ++#if U_PLATFORM_HAS_WINUWP_API + typedef PVOID LPMSG; // TODO: figure out how to get rid of this typedef + #include + #include +@@ -1062,53 +1061,13 @@ uprv_tzname_clear_cache() + #endif + } + +-// With the Universal Windows Platform we can just ask Windows for the name +-#if U_PLATFORM_HAS_WINUWP_API +-U_CAPI const char* U_EXPORT2 +-uprv_getWindowsTimeZone() +-{ +- // Get default Windows timezone. +- ComPtr calendar; +- HRESULT hr = RoActivateInstance( +- HStringReference(RuntimeClass_Windows_Globalization_Calendar).Get(), +- &calendar); +- if (SUCCEEDED(hr)) +- { +- ComPtr timezone; +- hr = calendar.As(&timezone); +- if (SUCCEEDED(hr)) +- { +- HString timezoneString; +- hr = timezone->GetTimeZone(timezoneString.GetAddressOf()); +- if (SUCCEEDED(hr)) +- { +- int32_t length = static_cast(wcslen(timezoneString.GetRawBuffer(NULL))); +- char* asciiId = (char*)uprv_calloc(length + 1, sizeof(char)); +- if (asciiId != nullptr) +- { +- u_UCharsToChars((UChar*)timezoneString.GetRawBuffer(NULL), asciiId, length); +- return asciiId; +- } +- } +- } +- } +- +- // Failed +- return nullptr; +-} +-#endif +- + U_CAPI const char* U_EXPORT2 + uprv_tzname(int n) + { + (void)n; // Avoid unreferenced parameter warning. + const char *tzid = NULL; + #if U_PLATFORM_USES_ONLY_WIN32_API +-#if U_PLATFORM_HAS_WINUWP_API > 0 +- tzid = uprv_getWindowsTimeZone(); +-#else + tzid = uprv_detectWindowsTimeZone(); +-#endif + + if (tzid != NULL) { + return tzid; +diff --git a/source/common/wintz.cpp b/source/common/wintz.cpp +index 3aaa36a0..9cf79d7b 100644 +--- a/source/common/wintz.cpp ++++ b/source/common/wintz.cpp +@@ -13,9 +13,7 @@ + + #include "unicode/utypes.h" + +-// This file contains only desktop Windows behavior +-// Windows UWP calls Windows::Globalization directly, so this isn't needed there. +-#if U_PLATFORM_USES_ONLY_WIN32_API && (U_PLATFORM_HAS_WINUWP_API == 0) ++#if U_PLATFORM_HAS_WIN32_API + + #include "wintz.h" + #include "cmemory.h" +@@ -23,6 +21,7 @@ + + #include "unicode/ures.h" + #include "unicode/ustring.h" ++#include "uresimp.h" + + #ifndef WIN32_LEAN_AND_MEAN + # define WIN32_LEAN_AND_MEAN +@@ -34,363 +33,94 @@ + # define NOMCX + #include + +-#define MAX_LENGTH_ID 40 ++U_NAMESPACE_BEGIN + +-/* The layout of the Tzi value in the registry */ +-typedef struct +-{ +- int32_t bias; +- int32_t standardBias; +- int32_t daylightBias; +- SYSTEMTIME standardDate; +- SYSTEMTIME daylightDate; +-} TZI; +- +-/** +- * Various registry keys and key fragments. +- */ +-static const wchar_t CURRENT_ZONE_REGKEY[] = L"SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation\\"; +-static const char STANDARD_TIME_REGKEY[] = " Standard Time"; +-static const char TZI_REGKEY[] = "TZI"; +-static const char STD_REGKEY[] = "Std"; ++// The value of MAX_TIMEZONE_ID_LENGTH is 128, which is defined in DYNAMIC_TIME_ZONE_INFORMATION ++#define MAX_TIMEZONE_ID_LENGTH 128 + + /** +- * The time zone root keys (under HKLM) for Win7+ +- */ +-static const char TZ_REGKEY[] = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\"; +- +-static LONG openTZRegKey(HKEY *hkey, const char *winid) +-{ +- char subKeyName[110]; /* TODO: why 110?? */ +- char *name; +- LONG result; +- +- uprv_strcpy(subKeyName, TZ_REGKEY); +- name = &subKeyName[strlen(subKeyName)]; +- uprv_strcat(subKeyName, winid); +- +- result = RegOpenKeyExA(HKEY_LOCAL_MACHINE, +- subKeyName, +- 0, +- KEY_QUERY_VALUE, +- hkey); +- return result; +-} +- +-static LONG getTZI(const char *winid, TZI *tzi) +-{ +- DWORD cbData = sizeof(TZI); +- LONG result; +- HKEY hkey; +- +- result = openTZRegKey(&hkey, winid); +- +- if (result == ERROR_SUCCESS) +- { +- result = RegQueryValueExA(hkey, +- TZI_REGKEY, +- NULL, +- NULL, +- (LPBYTE)tzi, +- &cbData); +- RegCloseKey(hkey); +- } +- +- return result; +-} +- +-static LONG getSTDName(const char *winid, char *regStdName, int32_t length) +-{ +- DWORD cbData = length; +- LONG result; +- HKEY hkey; +- +- result = openTZRegKey(&hkey, winid); +- +- if (result == ERROR_SUCCESS) +- { +- result = RegQueryValueExA(hkey, +- STD_REGKEY, +- NULL, +- NULL, +- (LPBYTE)regStdName, +- &cbData); +- RegCloseKey(hkey); +- } +- +- return result; +-} +- +-static LONG getTZKeyName(char* tzKeyName, int32_t tzKeyNamelength) +-{ +- HKEY hkey; +- LONG result = FALSE; +- WCHAR timeZoneKeyNameData[128]; +- DWORD timeZoneKeyNameLength = static_cast(sizeof(timeZoneKeyNameData)); +- +- if(ERROR_SUCCESS == RegOpenKeyExW( +- HKEY_LOCAL_MACHINE, +- CURRENT_ZONE_REGKEY, +- 0, +- KEY_QUERY_VALUE, +- &hkey)) +- { +- if (ERROR_SUCCESS == RegQueryValueExW( +- hkey, +- L"TimeZoneKeyName", +- NULL, +- NULL, +- (LPBYTE)timeZoneKeyNameData, +- &timeZoneKeyNameLength)) +- { +- // Ensure null termination. +- timeZoneKeyNameData[UPRV_LENGTHOF(timeZoneKeyNameData) - 1] = L'\0'; +- +- // Convert the UTF-16 string to UTF-8. +- UErrorCode status = U_ZERO_ERROR; +- u_strToUTF8(tzKeyName, tzKeyNamelength, NULL, reinterpret_cast(timeZoneKeyNameData), -1, &status); +- if (U_ZERO_ERROR == status) +- { +- result = ERROR_SUCCESS; +- } +- } +- RegCloseKey(hkey); +- } +- +- return result; +-} +- +-/* +- This code attempts to detect the Windows time zone directly, +- as set in the Windows Date and Time control panel. It attempts +- to work on versions greater than Windows Vista and on localized +- installs. It works by directly interrogating the registry and +- comparing the data there with the data returned by the +- GetTimeZoneInformation API, along with some other strategies. The +- registry contains time zone data under this key: +- +- HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\ +- +- Under this key are several subkeys, one for each time zone. For +- example these subkeys are named "Pacific Standard Time" on Vista+. +- There are some other wrinkles; see the code for +- details. The subkey name is NOT LOCALIZED, allowing us to support +- localized installs. +- +- Under the subkey are data values. We care about: +- +- Std Standard time display name, localized +- TZI Binary block of data +- +- The TZI data is of particular interest. It contains the offset, two +- more offsets for standard and daylight time, and the start and end +- rules. This is the same data returned by the GetTimeZoneInformation +- API. The API may modify the data on the way out, so we have to be +- careful, but essentially we do a binary comparison against the TZI +- blocks of various registry keys. When we find a match, we know what +- time zone Windows is set to. Since the registry key is not +- localized, we can then translate the key through a simple table +- lookup into the corresponding ICU time zone. +- +- This strategy doesn't always work because there are zones which +- share an offset and rules, so more than one TZI block will match. +- For example, both Tokyo and Seoul are at GMT+9 with no DST rules; +- their TZI blocks are identical. For these cases, we fall back to a +- name lookup. We attempt to match the display name as stored in the +- registry for the current zone to the display name stored in the +- registry for various Windows zones. By comparing the registry data +- directly we avoid conversion complications. +- +- Author: Alan Liu +- Since: ICU 2.6 +- Based on original code by Carl Brown ++* Main Windows time zone detection function. ++* Returns the Windows time zone converted to an ICU time zone as a heap-allocated buffer, or nullptr upon failure. ++* Note: We use the Win32 API GetDynamicTimeZoneInformation to get the current time zone info. ++* This API returns a non-localized time zone name, which we can then map to an ICU time zone name. + */ +- +-/** +- * Main Windows time zone detection function. Returns the Windows +- * time zone, translated to an ICU time zone, or NULL upon failure. +- */ + U_CFUNC const char* U_EXPORT2 +-uprv_detectWindowsTimeZone() ++uprv_detectWindowsTimeZone() + { + UErrorCode status = U_ZERO_ERROR; +- UResourceBundle* bundle = NULL; +- char* icuid = NULL; +- char apiStdName[MAX_LENGTH_ID]; +- char regStdName[MAX_LENGTH_ID]; +- char tmpid[MAX_LENGTH_ID]; ++ char* icuid = nullptr; ++ char dynamicTZKeyName[MAX_TIMEZONE_ID_LENGTH]; ++ char tmpid[MAX_TIMEZONE_ID_LENGTH]; + int32_t len; +- int id; ++ int id = GEOID_NOT_AVAILABLE; + int errorCode; +- wchar_t ISOcodeW[3]; /* 2 letter iso code in UTF-16*/ +- char ISOcodeA[3]; /* 2 letter iso code in ansi */ ++ wchar_t ISOcodeW[3] = {}; /* 2 letter ISO code in UTF-16 */ ++ char ISOcode[3] = {}; /* 2 letter ISO code in UTF-8 */ + +- LONG result; +- TZI tziKey; +- TZI tziReg; +- TIME_ZONE_INFORMATION apiTZI; ++ DYNAMIC_TIME_ZONE_INFORMATION dynamicTZI; ++ uprv_memset(&dynamicTZI, 0, sizeof(dynamicTZI)); ++ uprv_memset(dynamicTZKeyName, 0, sizeof(dynamicTZKeyName)); ++ uprv_memset(tmpid, 0, sizeof(tmpid)); + +- BOOL tryPreVistaFallback; +- OSVERSIONINFO osVerInfo; ++ /* Obtain TIME_ZONE_INFORMATION from the API and get the non-localized time zone name. */ ++ if (TIME_ZONE_ID_INVALID == GetDynamicTimeZoneInformation(&dynamicTZI)) { ++ return nullptr; ++ } + +- /* Obtain TIME_ZONE_INFORMATION from the API, and then convert it +- to TZI. We could also interrogate the registry directly; we do +- this below if needed. */ +- uprv_memset(&apiTZI, 0, sizeof(apiTZI)); +- uprv_memset(&tziKey, 0, sizeof(tziKey)); +- uprv_memset(&tziReg, 0, sizeof(tziReg)); +- GetTimeZoneInformation(&apiTZI); +- tziKey.bias = apiTZI.Bias; +- uprv_memcpy((char *)&tziKey.standardDate, (char*)&apiTZI.StandardDate, +- sizeof(apiTZI.StandardDate)); +- uprv_memcpy((char *)&tziKey.daylightDate, (char*)&apiTZI.DaylightDate, +- sizeof(apiTZI.DaylightDate)); ++ id = GetUserGeoID(GEOCLASS_NATION); ++ errorCode = GetGeoInfoW(id, GEO_ISO2, ISOcodeW, 3, 0); + +- /* Convert the wchar_t* standard name to char* */ +- uprv_memset(apiStdName, 0, sizeof(apiStdName)); +- wcstombs(apiStdName, apiTZI.StandardName, MAX_LENGTH_ID); ++ // convert from wchar_t* (UTF-16 on Windows) to char* (UTF-8). ++ u_strToUTF8(ISOcode, UPRV_LENGTHOF(ISOcode), nullptr, ++ reinterpret_cast(ISOcodeW), UPRV_LENGTHOF(ISOcodeW), &status); + +- tmpid[0] = 0; ++ LocalUResourceBundlePointer bundle(ures_openDirect(nullptr, "windowsZones", &status)); ++ ures_getByKey(bundle.getAlias(), "mapTimezones", bundle.getAlias(), &status); + +- id = GetUserGeoID(GEOCLASS_NATION); +- errorCode = GetGeoInfoW(id, GEO_ISO2, ISOcodeW, 3, 0); +- u_strToUTF8(ISOcodeA, 3, NULL, (const UChar *)ISOcodeW, 3, &status); ++ // convert from wchar_t* (UTF-16 on Windows) to char* (UTF-8). ++ u_strToUTF8(dynamicTZKeyName, UPRV_LENGTHOF(dynamicTZKeyName), nullptr, ++ reinterpret_cast(dynamicTZI.TimeZoneKeyName), UPRV_LENGTHOF(dynamicTZI.TimeZoneKeyName), &status); + +- bundle = ures_openDirect(NULL, "windowsZones", &status); +- ures_getByKey(bundle, "mapTimezones", bundle, &status); ++ if (U_FAILURE(status)) { ++ return nullptr; ++ } + +- /* +- Windows Vista+ provides us with a "TimeZoneKeyName" that is not localized +- and can be used to directly map a name in our bundle. Try to use that first +- if we're on Vista or higher +- */ +- uprv_memset(&osVerInfo, 0, sizeof(osVerInfo)); +- osVerInfo.dwOSVersionInfoSize = sizeof(osVerInfo); +- tryPreVistaFallback = TRUE; +- result = getTZKeyName(regStdName, sizeof(regStdName)); +- if(ERROR_SUCCESS == result) +- { +- UResourceBundle* winTZ = ures_getByKey(bundle, regStdName, NULL, &status); +- if(U_SUCCESS(status)) +- { +- const UChar* icuTZ = NULL; +- if (errorCode != 0) +- { +- icuTZ = ures_getStringByKey(winTZ, ISOcodeA, &len, &status); ++ if (dynamicTZI.TimeZoneKeyName[0] != 0) { ++ UResourceBundle winTZ; ++ ures_initStackObject(&winTZ); ++ ures_getByKey(bundle.getAlias(), dynamicTZKeyName, &winTZ, &status); ++ ++ if (U_SUCCESS(status)) { ++ const UChar* icuTZ = nullptr; ++ if (errorCode != 0) { ++ icuTZ = ures_getStringByKey(&winTZ, ISOcode, &len, &status); + } +- if (errorCode==0 || icuTZ==NULL) +- { ++ if (errorCode == 0 || icuTZ == nullptr) { + /* fallback to default "001" and reset status */ + status = U_ZERO_ERROR; +- icuTZ = ures_getStringByKey(winTZ, "001", &len, &status); +- } +- +- if(U_SUCCESS(status)) +- { +- int index=0; +- while (! (*icuTZ == '\0' || *icuTZ ==' ')) +- { +- tmpid[index++]=(char)(*icuTZ++); /* safe to assume 'char' is ASCII compatible on windows */ +- } +- tmpid[index]='\0'; +- tryPreVistaFallback = FALSE; +- } +- } +- ures_close(winTZ); +- } +- +- if(tryPreVistaFallback) +- { +- /* Note: We get the winid not from static tables but from resource bundle. */ +- while (U_SUCCESS(status) && ures_hasNext(bundle)) +- { +- UBool idFound = FALSE; +- const char* winid; +- UResourceBundle* winTZ = ures_getNextResource(bundle, NULL, &status); +- if (U_FAILURE(status)) +- { +- break; ++ icuTZ = ures_getStringByKey(&winTZ, "001", &len, &status); + } +- winid = ures_getKey(winTZ); +- result = getTZI(winid, &tziReg); +- +- if (result == ERROR_SUCCESS) +- { +- /* Windows alters the DaylightBias in some situations. +- Using the bias and the rules suffices, so overwrite +- these unreliable fields. */ +- tziKey.standardBias = tziReg.standardBias; +- tziKey.daylightBias = tziReg.daylightBias; + +- if (uprv_memcmp((char *)&tziKey, (char*)&tziReg, sizeof(tziKey)) == 0) +- { +- const UChar* icuTZ = NULL; +- if (errorCode != 0) +- { +- icuTZ = ures_getStringByKey(winTZ, ISOcodeA, &len, &status); +- } +- if (errorCode==0 || icuTZ==NULL) +- { +- /* fallback to default "001" and reset status */ +- status = U_ZERO_ERROR; +- icuTZ = ures_getStringByKey(winTZ, "001", &len, &status); +- } ++ if (U_SUCCESS(status)) { ++ int index = 0; + +- if (U_SUCCESS(status)) +- { +- /* Get the standard name from the registry key to compare with +- the one from Windows API call. */ +- uprv_memset(regStdName, 0, sizeof(regStdName)); +- result = getSTDName(winid, regStdName, sizeof(regStdName)); +- if (result == ERROR_SUCCESS) +- { +- if (uprv_strcmp(apiStdName, regStdName) == 0) +- { +- idFound = TRUE; +- } +- } +- +- /* tmpid buffer holds the ICU timezone ID corresponding to the timezone ID from Windows. +- * If none is found, tmpid buffer will contain a fallback ID (i.e. the time zone ID matching +- * the current time zone information) +- */ +- if (idFound || tmpid[0] == 0) +- { +- /* if icuTZ has more than one city, take only the first (i.e. terminate icuTZ at first space) */ +- int index=0; +- while (! (*icuTZ == '\0' || *icuTZ ==' ')) +- { +- tmpid[index++]=(char)(*icuTZ++); /* safe to assume 'char' is ASCII compatible on windows */ +- } +- tmpid[index]='\0'; +- } +- } ++ while (!(*icuTZ == '\0' || *icuTZ == ' ')) { ++ // time zone IDs only contain ASCII invariant characters. ++ tmpid[index++] = (char)(*icuTZ++); + } +- } +- ures_close(winTZ); +- if (idFound) +- { +- break; ++ tmpid[index] = '\0'; + } + } ++ ures_close(&winTZ); + } + +- /* +- * Copy the timezone ID to icuid to be returned. +- */ +- if (tmpid[0] != 0) +- { +- len = uprv_strlen(tmpid); +- icuid = (char*)uprv_calloc(len + 1, sizeof(char)); +- if (icuid != NULL) +- { +- uprv_strcpy(icuid, tmpid); +- } ++ // Copy the timezone ID to icuid to be returned. ++ if (tmpid[0] != 0) { ++ icuid = uprv_strdup(tmpid); + } + +- ures_close(bundle); +- + return icuid; + } + +-#endif /* U_PLATFORM_USES_ONLY_WIN32_API && (U_PLATFORM_HAS_WINUWP_API == 0) */ ++U_NAMESPACE_END ++#endif /* U_PLATFORM_HAS_WIN32_API */ +diff --git a/source/common/wintz.h b/source/common/wintz.h +index 7be30eb4..19b7cfe9 100644 +--- a/source/common/wintz.h ++++ b/source/common/wintz.h +@@ -16,9 +16,7 @@ + + #include "unicode/utypes.h" + +-// This file contains only desktop windows behavior +-// Windows UWP calls Windows::Globalization directly, so this isn't needed there. +-#if U_PLATFORM_USES_ONLY_WIN32_API && (U_PLATFORM_HAS_WINUWP_API == 0) ++#if U_PLATFORM_HAS_WIN32_API + + /** + * \file +@@ -33,6 +31,6 @@ U_CDECL_END + U_CFUNC const char* U_EXPORT2 + uprv_detectWindowsTimeZone(); + +-#endif /* U_PLATFORM_USES_ONLY_WIN32_API && (U_PLATFORM_HAS_WINUWP_API == 0) */ ++#endif /* U_PLATFORM_HAS_WIN32_API */ + + #endif /* __WINTZ */ diff --git a/source/common/putil.cpp b/source/common/putil.cpp index 2d2de524..229656d6 100644 --- a/source/common/putil.cpp +++ b/source/common/putil.cpp @@ -102,9 +102,8 @@ # define NOMCX # include # include "unicode/uloc.h" -#if U_PLATFORM_HAS_WINUWP_API == 0 # include "wintz.h" -#else // U_PLATFORM_HAS_WINUWP_API +#if U_PLATFORM_HAS_WINUWP_API typedef PVOID LPMSG; // TODO: figure out how to get rid of this typedef #include #include @@ -1062,53 +1061,13 @@ uprv_tzname_clear_cache() #endif } -// With the Universal Windows Platform we can just ask Windows for the name -#if U_PLATFORM_HAS_WINUWP_API -U_CAPI const char* U_EXPORT2 -uprv_getWindowsTimeZone() -{ - // Get default Windows timezone. - ComPtr calendar; - HRESULT hr = RoActivateInstance( - HStringReference(RuntimeClass_Windows_Globalization_Calendar).Get(), - &calendar); - if (SUCCEEDED(hr)) - { - ComPtr timezone; - hr = calendar.As(&timezone); - if (SUCCEEDED(hr)) - { - HString timezoneString; - hr = timezone->GetTimeZone(timezoneString.GetAddressOf()); - if (SUCCEEDED(hr)) - { - int32_t length = static_cast(wcslen(timezoneString.GetRawBuffer(NULL))); - char* asciiId = (char*)uprv_calloc(length + 1, sizeof(char)); - if (asciiId != nullptr) - { - u_UCharsToChars((UChar*)timezoneString.GetRawBuffer(NULL), asciiId, length); - return asciiId; - } - } - } - } - - // Failed - return nullptr; -} -#endif - U_CAPI const char* U_EXPORT2 uprv_tzname(int n) { (void)n; // Avoid unreferenced parameter warning. const char *tzid = NULL; #if U_PLATFORM_USES_ONLY_WIN32_API -#if U_PLATFORM_HAS_WINUWP_API > 0 - tzid = uprv_getWindowsTimeZone(); -#else tzid = uprv_detectWindowsTimeZone(); -#endif if (tzid != NULL) { return tzid; diff --git a/source/common/wintz.cpp b/source/common/wintz.cpp index 3aaa36a0..9cf79d7b 100644 --- a/source/common/wintz.cpp +++ b/source/common/wintz.cpp @@ -13,9 +13,7 @@ #include "unicode/utypes.h" -// This file contains only desktop Windows behavior -// Windows UWP calls Windows::Globalization directly, so this isn't needed there. -#if U_PLATFORM_USES_ONLY_WIN32_API && (U_PLATFORM_HAS_WINUWP_API == 0) +#if U_PLATFORM_HAS_WIN32_API #include "wintz.h" #include "cmemory.h" @@ -23,6 +21,7 @@ #include "unicode/ures.h" #include "unicode/ustring.h" +#include "uresimp.h" #ifndef WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN @@ -34,363 +33,94 @@ # define NOMCX #include -#define MAX_LENGTH_ID 40 +U_NAMESPACE_BEGIN -/* The layout of the Tzi value in the registry */ -typedef struct -{ - int32_t bias; - int32_t standardBias; - int32_t daylightBias; - SYSTEMTIME standardDate; - SYSTEMTIME daylightDate; -} TZI; - -/** - * Various registry keys and key fragments. - */ -static const wchar_t CURRENT_ZONE_REGKEY[] = L"SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation\\"; -static const char STANDARD_TIME_REGKEY[] = " Standard Time"; -static const char TZI_REGKEY[] = "TZI"; -static const char STD_REGKEY[] = "Std"; +// The value of MAX_TIMEZONE_ID_LENGTH is 128, which is defined in DYNAMIC_TIME_ZONE_INFORMATION +#define MAX_TIMEZONE_ID_LENGTH 128 /** - * The time zone root keys (under HKLM) for Win7+ - */ -static const char TZ_REGKEY[] = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\"; - -static LONG openTZRegKey(HKEY *hkey, const char *winid) -{ - char subKeyName[110]; /* TODO: why 110?? */ - char *name; - LONG result; - - uprv_strcpy(subKeyName, TZ_REGKEY); - name = &subKeyName[strlen(subKeyName)]; - uprv_strcat(subKeyName, winid); - - result = RegOpenKeyExA(HKEY_LOCAL_MACHINE, - subKeyName, - 0, - KEY_QUERY_VALUE, - hkey); - return result; -} - -static LONG getTZI(const char *winid, TZI *tzi) -{ - DWORD cbData = sizeof(TZI); - LONG result; - HKEY hkey; - - result = openTZRegKey(&hkey, winid); - - if (result == ERROR_SUCCESS) - { - result = RegQueryValueExA(hkey, - TZI_REGKEY, - NULL, - NULL, - (LPBYTE)tzi, - &cbData); - RegCloseKey(hkey); - } - - return result; -} - -static LONG getSTDName(const char *winid, char *regStdName, int32_t length) -{ - DWORD cbData = length; - LONG result; - HKEY hkey; - - result = openTZRegKey(&hkey, winid); - - if (result == ERROR_SUCCESS) - { - result = RegQueryValueExA(hkey, - STD_REGKEY, - NULL, - NULL, - (LPBYTE)regStdName, - &cbData); - RegCloseKey(hkey); - } - - return result; -} - -static LONG getTZKeyName(char* tzKeyName, int32_t tzKeyNamelength) -{ - HKEY hkey; - LONG result = FALSE; - WCHAR timeZoneKeyNameData[128]; - DWORD timeZoneKeyNameLength = static_cast(sizeof(timeZoneKeyNameData)); - - if(ERROR_SUCCESS == RegOpenKeyExW( - HKEY_LOCAL_MACHINE, - CURRENT_ZONE_REGKEY, - 0, - KEY_QUERY_VALUE, - &hkey)) - { - if (ERROR_SUCCESS == RegQueryValueExW( - hkey, - L"TimeZoneKeyName", - NULL, - NULL, - (LPBYTE)timeZoneKeyNameData, - &timeZoneKeyNameLength)) - { - // Ensure null termination. - timeZoneKeyNameData[UPRV_LENGTHOF(timeZoneKeyNameData) - 1] = L'\0'; - - // Convert the UTF-16 string to UTF-8. - UErrorCode status = U_ZERO_ERROR; - u_strToUTF8(tzKeyName, tzKeyNamelength, NULL, reinterpret_cast(timeZoneKeyNameData), -1, &status); - if (U_ZERO_ERROR == status) - { - result = ERROR_SUCCESS; - } - } - RegCloseKey(hkey); - } - - return result; -} - -/* - This code attempts to detect the Windows time zone directly, - as set in the Windows Date and Time control panel. It attempts - to work on versions greater than Windows Vista and on localized - installs. It works by directly interrogating the registry and - comparing the data there with the data returned by the - GetTimeZoneInformation API, along with some other strategies. The - registry contains time zone data under this key: - - HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\ - - Under this key are several subkeys, one for each time zone. For - example these subkeys are named "Pacific Standard Time" on Vista+. - There are some other wrinkles; see the code for - details. The subkey name is NOT LOCALIZED, allowing us to support - localized installs. - - Under the subkey are data values. We care about: - - Std Standard time display name, localized - TZI Binary block of data - - The TZI data is of particular interest. It contains the offset, two - more offsets for standard and daylight time, and the start and end - rules. This is the same data returned by the GetTimeZoneInformation - API. The API may modify the data on the way out, so we have to be - careful, but essentially we do a binary comparison against the TZI - blocks of various registry keys. When we find a match, we know what - time zone Windows is set to. Since the registry key is not - localized, we can then translate the key through a simple table - lookup into the corresponding ICU time zone. - - This strategy doesn't always work because there are zones which - share an offset and rules, so more than one TZI block will match. - For example, both Tokyo and Seoul are at GMT+9 with no DST rules; - their TZI blocks are identical. For these cases, we fall back to a - name lookup. We attempt to match the display name as stored in the - registry for the current zone to the display name stored in the - registry for various Windows zones. By comparing the registry data - directly we avoid conversion complications. - - Author: Alan Liu - Since: ICU 2.6 - Based on original code by Carl Brown +* Main Windows time zone detection function. +* Returns the Windows time zone converted to an ICU time zone as a heap-allocated buffer, or nullptr upon failure. +* Note: We use the Win32 API GetDynamicTimeZoneInformation to get the current time zone info. +* This API returns a non-localized time zone name, which we can then map to an ICU time zone name. */ - -/** - * Main Windows time zone detection function. Returns the Windows - * time zone, translated to an ICU time zone, or NULL upon failure. - */ U_CFUNC const char* U_EXPORT2 -uprv_detectWindowsTimeZone() +uprv_detectWindowsTimeZone() { UErrorCode status = U_ZERO_ERROR; - UResourceBundle* bundle = NULL; - char* icuid = NULL; - char apiStdName[MAX_LENGTH_ID]; - char regStdName[MAX_LENGTH_ID]; - char tmpid[MAX_LENGTH_ID]; + char* icuid = nullptr; + char dynamicTZKeyName[MAX_TIMEZONE_ID_LENGTH]; + char tmpid[MAX_TIMEZONE_ID_LENGTH]; int32_t len; - int id; + int id = GEOID_NOT_AVAILABLE; int errorCode; - wchar_t ISOcodeW[3]; /* 2 letter iso code in UTF-16*/ - char ISOcodeA[3]; /* 2 letter iso code in ansi */ + wchar_t ISOcodeW[3] = {}; /* 2 letter ISO code in UTF-16 */ + char ISOcode[3] = {}; /* 2 letter ISO code in UTF-8 */ - LONG result; - TZI tziKey; - TZI tziReg; - TIME_ZONE_INFORMATION apiTZI; + DYNAMIC_TIME_ZONE_INFORMATION dynamicTZI; + uprv_memset(&dynamicTZI, 0, sizeof(dynamicTZI)); + uprv_memset(dynamicTZKeyName, 0, sizeof(dynamicTZKeyName)); + uprv_memset(tmpid, 0, sizeof(tmpid)); - BOOL tryPreVistaFallback; - OSVERSIONINFO osVerInfo; + /* Obtain TIME_ZONE_INFORMATION from the API and get the non-localized time zone name. */ + if (TIME_ZONE_ID_INVALID == GetDynamicTimeZoneInformation(&dynamicTZI)) { + return nullptr; + } - /* Obtain TIME_ZONE_INFORMATION from the API, and then convert it - to TZI. We could also interrogate the registry directly; we do - this below if needed. */ - uprv_memset(&apiTZI, 0, sizeof(apiTZI)); - uprv_memset(&tziKey, 0, sizeof(tziKey)); - uprv_memset(&tziReg, 0, sizeof(tziReg)); - GetTimeZoneInformation(&apiTZI); - tziKey.bias = apiTZI.Bias; - uprv_memcpy((char *)&tziKey.standardDate, (char*)&apiTZI.StandardDate, - sizeof(apiTZI.StandardDate)); - uprv_memcpy((char *)&tziKey.daylightDate, (char*)&apiTZI.DaylightDate, - sizeof(apiTZI.DaylightDate)); + id = GetUserGeoID(GEOCLASS_NATION); + errorCode = GetGeoInfoW(id, GEO_ISO2, ISOcodeW, 3, 0); - /* Convert the wchar_t* standard name to char* */ - uprv_memset(apiStdName, 0, sizeof(apiStdName)); - wcstombs(apiStdName, apiTZI.StandardName, MAX_LENGTH_ID); + // convert from wchar_t* (UTF-16 on Windows) to char* (UTF-8). + u_strToUTF8(ISOcode, UPRV_LENGTHOF(ISOcode), nullptr, + reinterpret_cast(ISOcodeW), UPRV_LENGTHOF(ISOcodeW), &status); - tmpid[0] = 0; + LocalUResourceBundlePointer bundle(ures_openDirect(nullptr, "windowsZones", &status)); + ures_getByKey(bundle.getAlias(), "mapTimezones", bundle.getAlias(), &status); - id = GetUserGeoID(GEOCLASS_NATION); - errorCode = GetGeoInfoW(id, GEO_ISO2, ISOcodeW, 3, 0); - u_strToUTF8(ISOcodeA, 3, NULL, (const UChar *)ISOcodeW, 3, &status); + // convert from wchar_t* (UTF-16 on Windows) to char* (UTF-8). + u_strToUTF8(dynamicTZKeyName, UPRV_LENGTHOF(dynamicTZKeyName), nullptr, + reinterpret_cast(dynamicTZI.TimeZoneKeyName), UPRV_LENGTHOF(dynamicTZI.TimeZoneKeyName), &status); - bundle = ures_openDirect(NULL, "windowsZones", &status); - ures_getByKey(bundle, "mapTimezones", bundle, &status); + if (U_FAILURE(status)) { + return nullptr; + } - /* - Windows Vista+ provides us with a "TimeZoneKeyName" that is not localized - and can be used to directly map a name in our bundle. Try to use that first - if we're on Vista or higher - */ - uprv_memset(&osVerInfo, 0, sizeof(osVerInfo)); - osVerInfo.dwOSVersionInfoSize = sizeof(osVerInfo); - tryPreVistaFallback = TRUE; - result = getTZKeyName(regStdName, sizeof(regStdName)); - if(ERROR_SUCCESS == result) - { - UResourceBundle* winTZ = ures_getByKey(bundle, regStdName, NULL, &status); - if(U_SUCCESS(status)) - { - const UChar* icuTZ = NULL; - if (errorCode != 0) - { - icuTZ = ures_getStringByKey(winTZ, ISOcodeA, &len, &status); + if (dynamicTZI.TimeZoneKeyName[0] != 0) { + UResourceBundle winTZ; + ures_initStackObject(&winTZ); + ures_getByKey(bundle.getAlias(), dynamicTZKeyName, &winTZ, &status); + + if (U_SUCCESS(status)) { + const UChar* icuTZ = nullptr; + if (errorCode != 0) { + icuTZ = ures_getStringByKey(&winTZ, ISOcode, &len, &status); } - if (errorCode==0 || icuTZ==NULL) - { + if (errorCode == 0 || icuTZ == nullptr) { /* fallback to default "001" and reset status */ status = U_ZERO_ERROR; - icuTZ = ures_getStringByKey(winTZ, "001", &len, &status); - } - - if(U_SUCCESS(status)) - { - int index=0; - while (! (*icuTZ == '\0' || *icuTZ ==' ')) - { - tmpid[index++]=(char)(*icuTZ++); /* safe to assume 'char' is ASCII compatible on windows */ - } - tmpid[index]='\0'; - tryPreVistaFallback = FALSE; - } - } - ures_close(winTZ); - } - - if(tryPreVistaFallback) - { - /* Note: We get the winid not from static tables but from resource bundle. */ - while (U_SUCCESS(status) && ures_hasNext(bundle)) - { - UBool idFound = FALSE; - const char* winid; - UResourceBundle* winTZ = ures_getNextResource(bundle, NULL, &status); - if (U_FAILURE(status)) - { - break; + icuTZ = ures_getStringByKey(&winTZ, "001", &len, &status); } - winid = ures_getKey(winTZ); - result = getTZI(winid, &tziReg); - - if (result == ERROR_SUCCESS) - { - /* Windows alters the DaylightBias in some situations. - Using the bias and the rules suffices, so overwrite - these unreliable fields. */ - tziKey.standardBias = tziReg.standardBias; - tziKey.daylightBias = tziReg.daylightBias; - if (uprv_memcmp((char *)&tziKey, (char*)&tziReg, sizeof(tziKey)) == 0) - { - const UChar* icuTZ = NULL; - if (errorCode != 0) - { - icuTZ = ures_getStringByKey(winTZ, ISOcodeA, &len, &status); - } - if (errorCode==0 || icuTZ==NULL) - { - /* fallback to default "001" and reset status */ - status = U_ZERO_ERROR; - icuTZ = ures_getStringByKey(winTZ, "001", &len, &status); - } + if (U_SUCCESS(status)) { + int index = 0; - if (U_SUCCESS(status)) - { - /* Get the standard name from the registry key to compare with - the one from Windows API call. */ - uprv_memset(regStdName, 0, sizeof(regStdName)); - result = getSTDName(winid, regStdName, sizeof(regStdName)); - if (result == ERROR_SUCCESS) - { - if (uprv_strcmp(apiStdName, regStdName) == 0) - { - idFound = TRUE; - } - } - - /* tmpid buffer holds the ICU timezone ID corresponding to the timezone ID from Windows. - * If none is found, tmpid buffer will contain a fallback ID (i.e. the time zone ID matching - * the current time zone information) - */ - if (idFound || tmpid[0] == 0) - { - /* if icuTZ has more than one city, take only the first (i.e. terminate icuTZ at first space) */ - int index=0; - while (! (*icuTZ == '\0' || *icuTZ ==' ')) - { - tmpid[index++]=(char)(*icuTZ++); /* safe to assume 'char' is ASCII compatible on windows */ - } - tmpid[index]='\0'; - } - } + while (!(*icuTZ == '\0' || *icuTZ == ' ')) { + // time zone IDs only contain ASCII invariant characters. + tmpid[index++] = (char)(*icuTZ++); } - } - ures_close(winTZ); - if (idFound) - { - break; + tmpid[index] = '\0'; } } + ures_close(&winTZ); } - /* - * Copy the timezone ID to icuid to be returned. - */ - if (tmpid[0] != 0) - { - len = uprv_strlen(tmpid); - icuid = (char*)uprv_calloc(len + 1, sizeof(char)); - if (icuid != NULL) - { - uprv_strcpy(icuid, tmpid); - } + // Copy the timezone ID to icuid to be returned. + if (tmpid[0] != 0) { + icuid = uprv_strdup(tmpid); } - ures_close(bundle); - return icuid; } -#endif /* U_PLATFORM_USES_ONLY_WIN32_API && (U_PLATFORM_HAS_WINUWP_API == 0) */ +U_NAMESPACE_END +#endif /* U_PLATFORM_HAS_WIN32_API */ diff --git a/source/common/wintz.h b/source/common/wintz.h index 7be30eb4..19b7cfe9 100644 --- a/source/common/wintz.h +++ b/source/common/wintz.h @@ -16,9 +16,7 @@ #include "unicode/utypes.h" -// This file contains only desktop windows behavior -// Windows UWP calls Windows::Globalization directly, so this isn't needed there. -#if U_PLATFORM_USES_ONLY_WIN32_API && (U_PLATFORM_HAS_WINUWP_API == 0) +#if U_PLATFORM_HAS_WIN32_API /** * \file @@ -33,6 +31,6 @@ U_CDECL_END U_CFUNC const char* U_EXPORT2 uprv_detectWindowsTimeZone(); -#endif /* U_PLATFORM_USES_ONLY_WIN32_API && (U_PLATFORM_HAS_WINUWP_API == 0) */ +#endif /* U_PLATFORM_HAS_WIN32_API */ #endif /* __WINTZ */