1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/intl/icu/source/common/wintz.c Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,370 @@ 1.4 +/* 1.5 +******************************************************************************** 1.6 +* Copyright (C) 2005-2013, International Business Machines 1.7 +* Corporation and others. All Rights Reserved. 1.8 +******************************************************************************** 1.9 +* 1.10 +* File WINTZ.CPP 1.11 +* 1.12 +******************************************************************************** 1.13 +*/ 1.14 + 1.15 +#include "unicode/utypes.h" 1.16 + 1.17 +#if U_PLATFORM_HAS_WIN32_API 1.18 + 1.19 +#include "wintz.h" 1.20 +#include "cmemory.h" 1.21 +#include "cstring.h" 1.22 + 1.23 +#include "unicode/ustring.h" 1.24 +#include "unicode/ures.h" 1.25 + 1.26 +# define WIN32_LEAN_AND_MEAN 1.27 +# define VC_EXTRALEAN 1.28 +# define NOUSER 1.29 +# define NOSERVICE 1.30 +# define NOIME 1.31 +# define NOMCX 1.32 +#include <windows.h> 1.33 + 1.34 +#define MAX_LENGTH_ID 40 1.35 + 1.36 +/* The layout of the Tzi value in the registry */ 1.37 +typedef struct 1.38 +{ 1.39 + int32_t bias; 1.40 + int32_t standardBias; 1.41 + int32_t daylightBias; 1.42 + SYSTEMTIME standardDate; 1.43 + SYSTEMTIME daylightDate; 1.44 +} TZI; 1.45 + 1.46 +/** 1.47 + * Various registry keys and key fragments. 1.48 + */ 1.49 +static const char CURRENT_ZONE_REGKEY[] = "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation\\"; 1.50 +static const char STANDARD_NAME_REGKEY[] = "StandardName"; 1.51 +static const char STANDARD_TIME_REGKEY[] = " Standard Time"; 1.52 +static const char TZI_REGKEY[] = "TZI"; 1.53 +static const char STD_REGKEY[] = "Std"; 1.54 + 1.55 +/** 1.56 + * HKLM subkeys used to probe for the flavor of Windows. Note that we 1.57 + * specifically check for the "GMT" zone subkey; this is present on 1.58 + * NT, but on XP has become "GMT Standard Time". We need to 1.59 + * discriminate between these cases. 1.60 + */ 1.61 +static const char* const WIN_TYPE_PROBE_REGKEY[] = { 1.62 + /* WIN_9X_ME_TYPE */ 1.63 + "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones", 1.64 + 1.65 + /* WIN_NT_TYPE */ 1.66 + "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\GMT" 1.67 + 1.68 + /* otherwise: WIN_2K_XP_TYPE */ 1.69 +}; 1.70 + 1.71 +/** 1.72 + * The time zone root subkeys (under HKLM) for different flavors of 1.73 + * Windows. 1.74 + */ 1.75 +static const char* const TZ_REGKEY[] = { 1.76 + /* WIN_9X_ME_TYPE */ 1.77 + "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones\\", 1.78 + 1.79 + /* WIN_NT_TYPE | WIN_2K_XP_TYPE */ 1.80 + "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\" 1.81 +}; 1.82 + 1.83 +/** 1.84 + * Flavor of Windows, from our perspective. Not a real OS version, 1.85 + * but rather the flavor of the layout of the time zone information in 1.86 + * the registry. 1.87 + */ 1.88 +enum { 1.89 + WIN_9X_ME_TYPE = 1, 1.90 + WIN_NT_TYPE = 2, 1.91 + WIN_2K_XP_TYPE = 3 1.92 +}; 1.93 + 1.94 +static int32_t gWinType = 0; 1.95 + 1.96 +static int32_t detectWindowsType() 1.97 +{ 1.98 + int32_t winType; 1.99 + LONG result; 1.100 + HKEY hkey; 1.101 + 1.102 + /* Detect the version of windows by trying to open a sequence of 1.103 + probe keys. We don't use the OS version API because what we 1.104 + really want to know is how the registry is laid out. 1.105 + Specifically, is it 9x/Me or not, and is it "GMT" or "GMT 1.106 + Standard Time". */ 1.107 + for (winType = 0; winType < 2; winType++) { 1.108 + result = RegOpenKeyExA(HKEY_LOCAL_MACHINE, 1.109 + WIN_TYPE_PROBE_REGKEY[winType], 1.110 + 0, 1.111 + KEY_QUERY_VALUE, 1.112 + &hkey); 1.113 + RegCloseKey(hkey); 1.114 + 1.115 + if (result == ERROR_SUCCESS) { 1.116 + break; 1.117 + } 1.118 + } 1.119 + 1.120 + return winType+1; /* +1 to bring it inline with the enum */ 1.121 +} 1.122 + 1.123 +static LONG openTZRegKey(HKEY *hkey, const char *winid) 1.124 +{ 1.125 + char subKeyName[110]; /* TODO: why 96?? */ 1.126 + char *name; 1.127 + LONG result; 1.128 + 1.129 + /* This isn't thread safe, but it's good enough because the result should be constant per system. */ 1.130 + if (gWinType <= 0) { 1.131 + gWinType = detectWindowsType(); 1.132 + } 1.133 + 1.134 + uprv_strcpy(subKeyName, TZ_REGKEY[(gWinType != WIN_9X_ME_TYPE)]); 1.135 + name = &subKeyName[strlen(subKeyName)]; 1.136 + uprv_strcat(subKeyName, winid); 1.137 + 1.138 + if (gWinType == WIN_9X_ME_TYPE) { 1.139 + /* Remove " Standard Time" */ 1.140 + char *pStd = uprv_strstr(subKeyName, STANDARD_TIME_REGKEY); 1.141 + if (pStd) { 1.142 + *pStd = 0; 1.143 + } 1.144 + } 1.145 + 1.146 + result = RegOpenKeyExA(HKEY_LOCAL_MACHINE, 1.147 + subKeyName, 1.148 + 0, 1.149 + KEY_QUERY_VALUE, 1.150 + hkey); 1.151 + return result; 1.152 +} 1.153 + 1.154 +static LONG getTZI(const char *winid, TZI *tzi) 1.155 +{ 1.156 + DWORD cbData = sizeof(TZI); 1.157 + LONG result; 1.158 + HKEY hkey; 1.159 + 1.160 + result = openTZRegKey(&hkey, winid); 1.161 + 1.162 + if (result == ERROR_SUCCESS) { 1.163 + result = RegQueryValueExA(hkey, 1.164 + TZI_REGKEY, 1.165 + NULL, 1.166 + NULL, 1.167 + (LPBYTE)tzi, 1.168 + &cbData); 1.169 + 1.170 + } 1.171 + 1.172 + RegCloseKey(hkey); 1.173 + 1.174 + return result; 1.175 +} 1.176 + 1.177 +static LONG getSTDName(const char *winid, char *regStdName, int32_t length) { 1.178 + DWORD cbData = length; 1.179 + LONG result; 1.180 + HKEY hkey; 1.181 + 1.182 + result = openTZRegKey(&hkey, winid); 1.183 + 1.184 + if (result == ERROR_SUCCESS) { 1.185 + result = RegQueryValueExA(hkey, 1.186 + STD_REGKEY, 1.187 + NULL, 1.188 + NULL, 1.189 + (LPBYTE)regStdName, 1.190 + &cbData); 1.191 + 1.192 + } 1.193 + 1.194 + RegCloseKey(hkey); 1.195 + 1.196 + return result; 1.197 +} 1.198 + 1.199 +/* 1.200 + This code attempts to detect the Windows time zone, as set in the 1.201 + Windows Date and Time control panel. It attempts to work on 1.202 + multiple flavors of Windows (9x, Me, NT, 2000, XP) and on localized 1.203 + installs. It works by directly interrogating the registry and 1.204 + comparing the data there with the data returned by the 1.205 + GetTimeZoneInformation API, along with some other strategies. The 1.206 + registry contains time zone data under one of two keys (depending on 1.207 + the flavor of Windows): 1.208 + 1.209 + HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones\ 1.210 + HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\ 1.211 + 1.212 + Under this key are several subkeys, one for each time zone. These 1.213 + subkeys are named "Pacific" on Win9x/Me and "Pacific Standard Time" 1.214 + on WinNT/2k/XP. There are some other wrinkles; see the code for 1.215 + details. The subkey name is NOT LOCALIZED, allowing us to support 1.216 + localized installs. 1.217 + 1.218 + Under the subkey are data values. We care about: 1.219 + 1.220 + Std Standard time display name, localized 1.221 + TZI Binary block of data 1.222 + 1.223 + The TZI data is of particular interest. It contains the offset, two 1.224 + more offsets for standard and daylight time, and the start and end 1.225 + rules. This is the same data returned by the GetTimeZoneInformation 1.226 + API. The API may modify the data on the way out, so we have to be 1.227 + careful, but essentially we do a binary comparison against the TZI 1.228 + blocks of various registry keys. When we find a match, we know what 1.229 + time zone Windows is set to. Since the registry key is not 1.230 + localized, we can then translate the key through a simple table 1.231 + lookup into the corresponding ICU time zone. 1.232 + 1.233 + This strategy doesn't always work because there are zones which 1.234 + share an offset and rules, so more than one TZI block will match. 1.235 + For example, both Tokyo and Seoul are at GMT+9 with no DST rules; 1.236 + their TZI blocks are identical. For these cases, we fall back to a 1.237 + name lookup. We attempt to match the display name as stored in the 1.238 + registry for the current zone to the display name stored in the 1.239 + registry for various Windows zones. By comparing the registry data 1.240 + directly we avoid conversion complications. 1.241 + 1.242 + Author: Alan Liu 1.243 + Since: ICU 2.6 1.244 + Based on original code by Carl Brown <cbrown@xnetinc.com> 1.245 +*/ 1.246 + 1.247 +/** 1.248 + * Main Windows time zone detection function. Returns the Windows 1.249 + * time zone, translated to an ICU time zone, or NULL upon failure. 1.250 + */ 1.251 +U_CFUNC const char* U_EXPORT2 1.252 +uprv_detectWindowsTimeZone() { 1.253 + UErrorCode status = U_ZERO_ERROR; 1.254 + UResourceBundle* bundle = NULL; 1.255 + char* icuid = NULL; 1.256 + UChar apiStd[MAX_LENGTH_ID]; 1.257 + char apiStdName[MAX_LENGTH_ID]; 1.258 + char regStdName[MAX_LENGTH_ID]; 1.259 + char tmpid[MAX_LENGTH_ID]; 1.260 + int32_t len; 1.261 + int id; 1.262 + int errorCode; 1.263 + char ISOcode[3]; /* 2 letter iso code */ 1.264 + 1.265 + LONG result; 1.266 + TZI tziKey; 1.267 + TZI tziReg; 1.268 + TIME_ZONE_INFORMATION apiTZI; 1.269 + 1.270 + /* Obtain TIME_ZONE_INFORMATION from the API, and then convert it 1.271 + to TZI. We could also interrogate the registry directly; we do 1.272 + this below if needed. */ 1.273 + uprv_memset(&apiTZI, 0, sizeof(apiTZI)); 1.274 + uprv_memset(&tziKey, 0, sizeof(tziKey)); 1.275 + uprv_memset(&tziReg, 0, sizeof(tziReg)); 1.276 + GetTimeZoneInformation(&apiTZI); 1.277 + tziKey.bias = apiTZI.Bias; 1.278 + uprv_memcpy((char *)&tziKey.standardDate, (char*)&apiTZI.StandardDate, 1.279 + sizeof(apiTZI.StandardDate)); 1.280 + uprv_memcpy((char *)&tziKey.daylightDate, (char*)&apiTZI.DaylightDate, 1.281 + sizeof(apiTZI.DaylightDate)); 1.282 + 1.283 + /* Convert the wchar_t* standard name to char* */ 1.284 + uprv_memset(apiStdName, 0, sizeof(apiStdName)); 1.285 + u_strFromWCS(apiStd, MAX_LENGTH_ID, NULL, apiTZI.StandardName, -1, &status); 1.286 + u_austrncpy(apiStdName, apiStd, sizeof(apiStdName) - 1); 1.287 + 1.288 + tmpid[0] = 0; 1.289 + 1.290 + id = GetUserGeoID(GEOCLASS_NATION); 1.291 + errorCode = GetGeoInfo(id,GEO_ISO2,ISOcode,3,0); 1.292 + 1.293 + bundle = ures_openDirect(NULL, "windowsZones", &status); 1.294 + ures_getByKey(bundle, "mapTimezones", bundle, &status); 1.295 + 1.296 + /* Note: We get the winid not from static tables but from resource bundle. */ 1.297 + while (U_SUCCESS(status) && ures_hasNext(bundle)) { 1.298 + UBool idFound = FALSE; 1.299 + const char* winid; 1.300 + UResourceBundle* winTZ = ures_getNextResource(bundle, NULL, &status); 1.301 + if (U_FAILURE(status)) { 1.302 + break; 1.303 + } 1.304 + winid = ures_getKey(winTZ); 1.305 + result = getTZI(winid, &tziReg); 1.306 + 1.307 + if (result == ERROR_SUCCESS) { 1.308 + /* Windows alters the DaylightBias in some situations. 1.309 + Using the bias and the rules suffices, so overwrite 1.310 + these unreliable fields. */ 1.311 + tziKey.standardBias = tziReg.standardBias; 1.312 + tziKey.daylightBias = tziReg.daylightBias; 1.313 + 1.314 + if (uprv_memcmp((char *)&tziKey, (char*)&tziReg, sizeof(tziKey)) == 0) { 1.315 + const UChar* icuTZ = NULL; 1.316 + if (errorCode != 0) { 1.317 + icuTZ = ures_getStringByKey(winTZ, ISOcode, &len, &status); 1.318 + } 1.319 + if (errorCode==0 || icuTZ==NULL) { 1.320 + /* fallback to default "001" and reset status */ 1.321 + status = U_ZERO_ERROR; 1.322 + icuTZ = ures_getStringByKey(winTZ, "001", &len, &status); 1.323 + } 1.324 + 1.325 + if (U_SUCCESS(status)) { 1.326 + /* Get the standard name from the registry key to compare with 1.327 + the one from Windows API call. */ 1.328 + uprv_memset(regStdName, 0, sizeof(regStdName)); 1.329 + result = getSTDName(winid, regStdName, sizeof(regStdName)); 1.330 + if (result == ERROR_SUCCESS) { 1.331 + if (uprv_strcmp(apiStdName, regStdName) == 0) { 1.332 + idFound = TRUE; 1.333 + } 1.334 + } 1.335 + 1.336 + /* tmpid buffer holds the ICU timezone ID corresponding to the timezone ID from Windows. 1.337 + * If none is found, tmpid buffer will contain a fallback ID (i.e. the time zone ID matching 1.338 + * the current time zone information) 1.339 + */ 1.340 + if (idFound || tmpid[0] == 0) { 1.341 + /* if icuTZ has more than one city, take only the first (i.e. terminate icuTZ at first space) */ 1.342 + int index=0; 1.343 + while (! (*icuTZ == '\0' || *icuTZ ==' ')) { 1.344 + tmpid[index++]=(char)(*icuTZ++); /* safe to assume 'char' is ASCII compatible on windows */ 1.345 + } 1.346 + tmpid[index]='\0'; 1.347 + } 1.348 + } 1.349 + } 1.350 + } 1.351 + ures_close(winTZ); 1.352 + if (idFound) { 1.353 + break; 1.354 + } 1.355 + } 1.356 + 1.357 + /* 1.358 + * Copy the timezone ID to icuid to be returned. 1.359 + */ 1.360 + if (tmpid[0] != 0) { 1.361 + len = uprv_strlen(tmpid); 1.362 + icuid = (char*)uprv_calloc(len + 1, sizeof(char)); 1.363 + if (icuid != NULL) { 1.364 + uprv_strcpy(icuid, tmpid); 1.365 + } 1.366 + } 1.367 + 1.368 + ures_close(bundle); 1.369 + 1.370 + return icuid; 1.371 +} 1.372 + 1.373 +#endif /* U_PLATFORM_HAS_WIN32_API */