| /* |
| ******************************************************************************** |
| * Copyright (C) 2005-2015, International Business Machines |
| * Corporation and others. All Rights Reserved. |
| ******************************************************************************** |
| * |
| * File WINTZ.CPP |
| * |
| ******************************************************************************** |
| */ |
| |
| #include "unicode/utypes.h" |
| |
| #if U_PLATFORM_HAS_WIN32_API |
| |
| #include "wintz.h" |
| #include "cmemory.h" |
| #include "cstring.h" |
| |
| #include "unicode/ures.h" |
| #include "unicode/ustring.h" |
| |
| # define WIN32_LEAN_AND_MEAN |
| # define VC_EXTRALEAN |
| # define NOUSER |
| # define NOSERVICE |
| # define NOIME |
| # define NOMCX |
| #include <windows.h> |
| |
| #define MAX_LENGTH_ID 40 |
| |
| /* 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 char CURRENT_ZONE_REGKEY[] = "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation\\"; |
| /* static const char STANDARD_NAME_REGKEY[] = "StandardName"; Currently unused constant */ |
| static const char STANDARD_TIME_REGKEY[] = " Standard Time"; |
| static const char TZI_REGKEY[] = "TZI"; |
| static const char STD_REGKEY[] = "Std"; |
| |
| /** |
| * HKLM subkeys used to probe for the flavor of Windows. Note that we |
| * specifically check for the "GMT" zone subkey; this is present on |
| * NT, but on XP has become "GMT Standard Time". We need to |
| * discriminate between these cases. |
| */ |
| static const char* const WIN_TYPE_PROBE_REGKEY[] = { |
| /* WIN_9X_ME_TYPE */ |
| "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones", |
| |
| /* WIN_NT_TYPE */ |
| "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\GMT" |
| |
| /* otherwise: WIN_2K_XP_TYPE */ |
| }; |
| |
| /** |
| * The time zone root subkeys (under HKLM) for different flavors of |
| * Windows. |
| */ |
| static const char* const TZ_REGKEY[] = { |
| /* WIN_9X_ME_TYPE */ |
| "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones\\", |
| |
| /* WIN_NT_TYPE | WIN_2K_XP_TYPE */ |
| "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\" |
| }; |
| |
| /** |
| * Flavor of Windows, from our perspective. Not a real OS version, |
| * but rather the flavor of the layout of the time zone information in |
| * the registry. |
| */ |
| enum { |
| WIN_9X_ME_TYPE = 1, |
| WIN_NT_TYPE = 2, |
| WIN_2K_XP_TYPE = 3 |
| }; |
| |
| static int32_t gWinType = 0; |
| |
| static int32_t detectWindowsType() |
| { |
| int32_t winType; |
| LONG result; |
| HKEY hkey; |
| |
| /* Detect the version of windows by trying to open a sequence of |
| probe keys. We don't use the OS version API because what we |
| really want to know is how the registry is laid out. |
| Specifically, is it 9x/Me or not, and is it "GMT" or "GMT |
| Standard Time". */ |
| for (winType = 0; winType < 2; winType++) { |
| result = RegOpenKeyExA(HKEY_LOCAL_MACHINE, |
| WIN_TYPE_PROBE_REGKEY[winType], |
| 0, |
| KEY_QUERY_VALUE, |
| &hkey); |
| RegCloseKey(hkey); |
| |
| if (result == ERROR_SUCCESS) { |
| break; |
| } |
| } |
| |
| return winType+1; /* +1 to bring it inline with the enum */ |
| } |
| |
| static LONG openTZRegKey(HKEY *hkey, const char *winid) |
| { |
| char subKeyName[110]; /* TODO: why 96?? */ |
| char *name; |
| LONG result; |
| |
| /* This isn't thread safe, but it's good enough because the result should be constant per system. */ |
| if (gWinType <= 0) { |
| gWinType = detectWindowsType(); |
| } |
| |
| uprv_strcpy(subKeyName, TZ_REGKEY[(gWinType != WIN_9X_ME_TYPE)]); |
| name = &subKeyName[strlen(subKeyName)]; |
| uprv_strcat(subKeyName, winid); |
| |
| if (gWinType == WIN_9X_ME_TYPE) { |
| /* Remove " Standard Time" */ |
| char *pStd = uprv_strstr(subKeyName, STANDARD_TIME_REGKEY); |
| if (pStd) { |
| *pStd = 0; |
| } |
| } |
| |
| 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 length) { |
| HKEY hkey; |
| LONG result = FALSE; |
| DWORD cbData = length; |
| |
| if(ERROR_SUCCESS == RegOpenKeyExA( |
| HKEY_LOCAL_MACHINE, |
| CURRENT_ZONE_REGKEY, |
| 0, |
| KEY_QUERY_VALUE, |
| &hkey)) |
| { |
| result = RegQueryValueExA( |
| hkey, |
| "TimeZoneKeyName", |
| NULL, |
| NULL, |
| (LPBYTE)tzKeyName, |
| &cbData); |
| } |
| |
| return result; |
| } |
| |
| /* |
| This code attempts to detect the Windows time zone, as set in the |
| Windows Date and Time control panel. It attempts to work on |
| multiple flavors of Windows (9x, Me, NT, 2000, XP) 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 one of two keys (depending on |
| the flavor of Windows): |
| |
| HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones\ |
| HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\ |
| |
| Under this key are several subkeys, one for each time zone. These |
| subkeys are named "Pacific" on Win9x/Me and "Pacific Standard Time" |
| on WinNT/2k/XP. 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 <cbrown@xnetinc.com> |
| */ |
| |
| /** |
| * 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() { |
| 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]; |
| int32_t len; |
| int id; |
| int errorCode; |
| UChar ISOcodeW[3]; /* 2 letter iso code in UTF-16*/ |
| char ISOcodeA[3]; /* 2 letter iso code in ansi */ |
| |
| LONG result; |
| TZI tziKey; |
| TZI tziReg; |
| TIME_ZONE_INFORMATION apiTZI; |
| |
| BOOL isVistaOrHigher; |
| BOOL tryPreVistaFallback; |
| OSVERSIONINFO osVerInfo; |
| |
| /* 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)); |
| |
| /* Convert the wchar_t* standard name to char* */ |
| uprv_memset(apiStdName, 0, sizeof(apiStdName)); |
| wcstombs(apiStdName, apiTZI.StandardName, MAX_LENGTH_ID); |
| |
| tmpid[0] = 0; |
| |
| id = GetUserGeoID(GEOCLASS_NATION); |
| errorCode = GetGeoInfoW(id,GEO_ISO2,ISOcodeW,3,0); |
| u_strToUTF8(ISOcodeA, 3, NULL, ISOcodeW, 3, &status); |
| |
| bundle = ures_openDirect(NULL, "windowsZones", &status); |
| ures_getByKey(bundle, "mapTimezones", bundle, &status); |
| |
| /* |
| 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); |
| GetVersionEx(&osVerInfo); |
| isVistaOrHigher = osVerInfo.dwMajorVersion >= 6; /* actually includes Windows Server 2008 as well, but don't worry about it */ |
| tryPreVistaFallback = TRUE; |
| if(isVistaOrHigher) { |
| 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 (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; |
| while (! (*icuTZ == '\0' || *icuTZ ==' ')) { |
| tmpid[index++]=(char)(*icuTZ++); /* safe to assume 'char' is ASCII compatible on windows */ |
| } |
| tmpid[index]='\0'; |
| tryPreVistaFallback = FALSE; |
| } |
| } |
| } |
| } |
| |
| 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; |
| } |
| 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)) { |
| /* 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'; |
| } |
| } |
| } |
| } |
| ures_close(winTZ); |
| if (idFound) { |
| break; |
| } |
| } |
| } |
| |
| /* |
| * 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); |
| } |
| } |
| |
| ures_close(bundle); |
| |
| return icuid; |
| } |
| |
| #endif /* U_PLATFORM_HAS_WIN32_API */ |