michael@0: /* michael@0: ******************************************************************************** michael@0: * Copyright (C) 2005-2013, International Business Machines michael@0: * Corporation and others. All Rights Reserved. michael@0: ******************************************************************************** michael@0: * michael@0: * File WINTZ.CPP michael@0: * michael@0: ******************************************************************************** michael@0: */ michael@0: michael@0: #include "unicode/utypes.h" michael@0: michael@0: #if U_PLATFORM_HAS_WIN32_API michael@0: michael@0: #include "wintz.h" michael@0: #include "cmemory.h" michael@0: #include "cstring.h" michael@0: michael@0: #include "unicode/ustring.h" michael@0: #include "unicode/ures.h" michael@0: michael@0: # define WIN32_LEAN_AND_MEAN michael@0: # define VC_EXTRALEAN michael@0: # define NOUSER michael@0: # define NOSERVICE michael@0: # define NOIME michael@0: # define NOMCX michael@0: #include michael@0: michael@0: #define MAX_LENGTH_ID 40 michael@0: michael@0: /* The layout of the Tzi value in the registry */ michael@0: typedef struct michael@0: { michael@0: int32_t bias; michael@0: int32_t standardBias; michael@0: int32_t daylightBias; michael@0: SYSTEMTIME standardDate; michael@0: SYSTEMTIME daylightDate; michael@0: } TZI; michael@0: michael@0: /** michael@0: * Various registry keys and key fragments. michael@0: */ michael@0: static const char CURRENT_ZONE_REGKEY[] = "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation\\"; michael@0: static const char STANDARD_NAME_REGKEY[] = "StandardName"; michael@0: static const char STANDARD_TIME_REGKEY[] = " Standard Time"; michael@0: static const char TZI_REGKEY[] = "TZI"; michael@0: static const char STD_REGKEY[] = "Std"; michael@0: michael@0: /** michael@0: * HKLM subkeys used to probe for the flavor of Windows. Note that we michael@0: * specifically check for the "GMT" zone subkey; this is present on michael@0: * NT, but on XP has become "GMT Standard Time". We need to michael@0: * discriminate between these cases. michael@0: */ michael@0: static const char* const WIN_TYPE_PROBE_REGKEY[] = { michael@0: /* WIN_9X_ME_TYPE */ michael@0: "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones", michael@0: michael@0: /* WIN_NT_TYPE */ michael@0: "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\GMT" michael@0: michael@0: /* otherwise: WIN_2K_XP_TYPE */ michael@0: }; michael@0: michael@0: /** michael@0: * The time zone root subkeys (under HKLM) for different flavors of michael@0: * Windows. michael@0: */ michael@0: static const char* const TZ_REGKEY[] = { michael@0: /* WIN_9X_ME_TYPE */ michael@0: "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones\\", michael@0: michael@0: /* WIN_NT_TYPE | WIN_2K_XP_TYPE */ michael@0: "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\" michael@0: }; michael@0: michael@0: /** michael@0: * Flavor of Windows, from our perspective. Not a real OS version, michael@0: * but rather the flavor of the layout of the time zone information in michael@0: * the registry. michael@0: */ michael@0: enum { michael@0: WIN_9X_ME_TYPE = 1, michael@0: WIN_NT_TYPE = 2, michael@0: WIN_2K_XP_TYPE = 3 michael@0: }; michael@0: michael@0: static int32_t gWinType = 0; michael@0: michael@0: static int32_t detectWindowsType() michael@0: { michael@0: int32_t winType; michael@0: LONG result; michael@0: HKEY hkey; michael@0: michael@0: /* Detect the version of windows by trying to open a sequence of michael@0: probe keys. We don't use the OS version API because what we michael@0: really want to know is how the registry is laid out. michael@0: Specifically, is it 9x/Me or not, and is it "GMT" or "GMT michael@0: Standard Time". */ michael@0: for (winType = 0; winType < 2; winType++) { michael@0: result = RegOpenKeyExA(HKEY_LOCAL_MACHINE, michael@0: WIN_TYPE_PROBE_REGKEY[winType], michael@0: 0, michael@0: KEY_QUERY_VALUE, michael@0: &hkey); michael@0: RegCloseKey(hkey); michael@0: michael@0: if (result == ERROR_SUCCESS) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return winType+1; /* +1 to bring it inline with the enum */ michael@0: } michael@0: michael@0: static LONG openTZRegKey(HKEY *hkey, const char *winid) michael@0: { michael@0: char subKeyName[110]; /* TODO: why 96?? */ michael@0: char *name; michael@0: LONG result; michael@0: michael@0: /* This isn't thread safe, but it's good enough because the result should be constant per system. */ michael@0: if (gWinType <= 0) { michael@0: gWinType = detectWindowsType(); michael@0: } michael@0: michael@0: uprv_strcpy(subKeyName, TZ_REGKEY[(gWinType != WIN_9X_ME_TYPE)]); michael@0: name = &subKeyName[strlen(subKeyName)]; michael@0: uprv_strcat(subKeyName, winid); michael@0: michael@0: if (gWinType == WIN_9X_ME_TYPE) { michael@0: /* Remove " Standard Time" */ michael@0: char *pStd = uprv_strstr(subKeyName, STANDARD_TIME_REGKEY); michael@0: if (pStd) { michael@0: *pStd = 0; michael@0: } michael@0: } michael@0: michael@0: result = RegOpenKeyExA(HKEY_LOCAL_MACHINE, michael@0: subKeyName, michael@0: 0, michael@0: KEY_QUERY_VALUE, michael@0: hkey); michael@0: return result; michael@0: } michael@0: michael@0: static LONG getTZI(const char *winid, TZI *tzi) michael@0: { michael@0: DWORD cbData = sizeof(TZI); michael@0: LONG result; michael@0: HKEY hkey; michael@0: michael@0: result = openTZRegKey(&hkey, winid); michael@0: michael@0: if (result == ERROR_SUCCESS) { michael@0: result = RegQueryValueExA(hkey, michael@0: TZI_REGKEY, michael@0: NULL, michael@0: NULL, michael@0: (LPBYTE)tzi, michael@0: &cbData); michael@0: michael@0: } michael@0: michael@0: RegCloseKey(hkey); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: static LONG getSTDName(const char *winid, char *regStdName, int32_t length) { michael@0: DWORD cbData = length; michael@0: LONG result; michael@0: HKEY hkey; michael@0: michael@0: result = openTZRegKey(&hkey, winid); michael@0: michael@0: if (result == ERROR_SUCCESS) { michael@0: result = RegQueryValueExA(hkey, michael@0: STD_REGKEY, michael@0: NULL, michael@0: NULL, michael@0: (LPBYTE)regStdName, michael@0: &cbData); michael@0: michael@0: } michael@0: michael@0: RegCloseKey(hkey); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: /* michael@0: This code attempts to detect the Windows time zone, as set in the michael@0: Windows Date and Time control panel. It attempts to work on michael@0: multiple flavors of Windows (9x, Me, NT, 2000, XP) and on localized michael@0: installs. It works by directly interrogating the registry and michael@0: comparing the data there with the data returned by the michael@0: GetTimeZoneInformation API, along with some other strategies. The michael@0: registry contains time zone data under one of two keys (depending on michael@0: the flavor of Windows): michael@0: michael@0: HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones\ michael@0: HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\ michael@0: michael@0: Under this key are several subkeys, one for each time zone. These michael@0: subkeys are named "Pacific" on Win9x/Me and "Pacific Standard Time" michael@0: on WinNT/2k/XP. There are some other wrinkles; see the code for michael@0: details. The subkey name is NOT LOCALIZED, allowing us to support michael@0: localized installs. michael@0: michael@0: Under the subkey are data values. We care about: michael@0: michael@0: Std Standard time display name, localized michael@0: TZI Binary block of data michael@0: michael@0: The TZI data is of particular interest. It contains the offset, two michael@0: more offsets for standard and daylight time, and the start and end michael@0: rules. This is the same data returned by the GetTimeZoneInformation michael@0: API. The API may modify the data on the way out, so we have to be michael@0: careful, but essentially we do a binary comparison against the TZI michael@0: blocks of various registry keys. When we find a match, we know what michael@0: time zone Windows is set to. Since the registry key is not michael@0: localized, we can then translate the key through a simple table michael@0: lookup into the corresponding ICU time zone. michael@0: michael@0: This strategy doesn't always work because there are zones which michael@0: share an offset and rules, so more than one TZI block will match. michael@0: For example, both Tokyo and Seoul are at GMT+9 with no DST rules; michael@0: their TZI blocks are identical. For these cases, we fall back to a michael@0: name lookup. We attempt to match the display name as stored in the michael@0: registry for the current zone to the display name stored in the michael@0: registry for various Windows zones. By comparing the registry data michael@0: directly we avoid conversion complications. michael@0: michael@0: Author: Alan Liu michael@0: Since: ICU 2.6 michael@0: Based on original code by Carl Brown michael@0: */ michael@0: michael@0: /** michael@0: * Main Windows time zone detection function. Returns the Windows michael@0: * time zone, translated to an ICU time zone, or NULL upon failure. michael@0: */ michael@0: U_CFUNC const char* U_EXPORT2 michael@0: uprv_detectWindowsTimeZone() { michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: UResourceBundle* bundle = NULL; michael@0: char* icuid = NULL; michael@0: UChar apiStd[MAX_LENGTH_ID]; michael@0: char apiStdName[MAX_LENGTH_ID]; michael@0: char regStdName[MAX_LENGTH_ID]; michael@0: char tmpid[MAX_LENGTH_ID]; michael@0: int32_t len; michael@0: int id; michael@0: int errorCode; michael@0: char ISOcode[3]; /* 2 letter iso code */ michael@0: michael@0: LONG result; michael@0: TZI tziKey; michael@0: TZI tziReg; michael@0: TIME_ZONE_INFORMATION apiTZI; michael@0: michael@0: /* Obtain TIME_ZONE_INFORMATION from the API, and then convert it michael@0: to TZI. We could also interrogate the registry directly; we do michael@0: this below if needed. */ michael@0: uprv_memset(&apiTZI, 0, sizeof(apiTZI)); michael@0: uprv_memset(&tziKey, 0, sizeof(tziKey)); michael@0: uprv_memset(&tziReg, 0, sizeof(tziReg)); michael@0: GetTimeZoneInformation(&apiTZI); michael@0: tziKey.bias = apiTZI.Bias; michael@0: uprv_memcpy((char *)&tziKey.standardDate, (char*)&apiTZI.StandardDate, michael@0: sizeof(apiTZI.StandardDate)); michael@0: uprv_memcpy((char *)&tziKey.daylightDate, (char*)&apiTZI.DaylightDate, michael@0: sizeof(apiTZI.DaylightDate)); michael@0: michael@0: /* Convert the wchar_t* standard name to char* */ michael@0: uprv_memset(apiStdName, 0, sizeof(apiStdName)); michael@0: u_strFromWCS(apiStd, MAX_LENGTH_ID, NULL, apiTZI.StandardName, -1, &status); michael@0: u_austrncpy(apiStdName, apiStd, sizeof(apiStdName) - 1); michael@0: michael@0: tmpid[0] = 0; michael@0: michael@0: id = GetUserGeoID(GEOCLASS_NATION); michael@0: errorCode = GetGeoInfo(id,GEO_ISO2,ISOcode,3,0); michael@0: michael@0: bundle = ures_openDirect(NULL, "windowsZones", &status); michael@0: ures_getByKey(bundle, "mapTimezones", bundle, &status); michael@0: michael@0: /* Note: We get the winid not from static tables but from resource bundle. */ michael@0: while (U_SUCCESS(status) && ures_hasNext(bundle)) { michael@0: UBool idFound = FALSE; michael@0: const char* winid; michael@0: UResourceBundle* winTZ = ures_getNextResource(bundle, NULL, &status); michael@0: if (U_FAILURE(status)) { michael@0: break; michael@0: } michael@0: winid = ures_getKey(winTZ); michael@0: result = getTZI(winid, &tziReg); michael@0: michael@0: if (result == ERROR_SUCCESS) { michael@0: /* Windows alters the DaylightBias in some situations. michael@0: Using the bias and the rules suffices, so overwrite michael@0: these unreliable fields. */ michael@0: tziKey.standardBias = tziReg.standardBias; michael@0: tziKey.daylightBias = tziReg.daylightBias; michael@0: michael@0: if (uprv_memcmp((char *)&tziKey, (char*)&tziReg, sizeof(tziKey)) == 0) { michael@0: const UChar* icuTZ = NULL; michael@0: if (errorCode != 0) { michael@0: icuTZ = ures_getStringByKey(winTZ, ISOcode, &len, &status); michael@0: } michael@0: if (errorCode==0 || icuTZ==NULL) { michael@0: /* fallback to default "001" and reset status */ michael@0: status = U_ZERO_ERROR; michael@0: icuTZ = ures_getStringByKey(winTZ, "001", &len, &status); michael@0: } michael@0: michael@0: if (U_SUCCESS(status)) { michael@0: /* Get the standard name from the registry key to compare with michael@0: the one from Windows API call. */ michael@0: uprv_memset(regStdName, 0, sizeof(regStdName)); michael@0: result = getSTDName(winid, regStdName, sizeof(regStdName)); michael@0: if (result == ERROR_SUCCESS) { michael@0: if (uprv_strcmp(apiStdName, regStdName) == 0) { michael@0: idFound = TRUE; michael@0: } michael@0: } michael@0: michael@0: /* tmpid buffer holds the ICU timezone ID corresponding to the timezone ID from Windows. michael@0: * If none is found, tmpid buffer will contain a fallback ID (i.e. the time zone ID matching michael@0: * the current time zone information) michael@0: */ michael@0: if (idFound || tmpid[0] == 0) { michael@0: /* if icuTZ has more than one city, take only the first (i.e. terminate icuTZ at first space) */ michael@0: int index=0; michael@0: while (! (*icuTZ == '\0' || *icuTZ ==' ')) { michael@0: tmpid[index++]=(char)(*icuTZ++); /* safe to assume 'char' is ASCII compatible on windows */ michael@0: } michael@0: tmpid[index]='\0'; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: ures_close(winTZ); michael@0: if (idFound) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Copy the timezone ID to icuid to be returned. michael@0: */ michael@0: if (tmpid[0] != 0) { michael@0: len = uprv_strlen(tmpid); michael@0: icuid = (char*)uprv_calloc(len + 1, sizeof(char)); michael@0: if (icuid != NULL) { michael@0: uprv_strcpy(icuid, tmpid); michael@0: } michael@0: } michael@0: michael@0: ures_close(bundle); michael@0: michael@0: return icuid; michael@0: } michael@0: michael@0: #endif /* U_PLATFORM_HAS_WIN32_API */