1 // 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 3 /* 4 ******************************************************************************** 5 * Copyright (C) 2005-2015, International Business Machines 6 * Corporation and others. All Rights Reserved. 7 ******************************************************************************** 8 * 9 * File WINTZ.CPP 10 * 11 ******************************************************************************** 12 */ 13 14 #include "unicode/utypes.h" 15 16 // This file contains only desktop Windows behavior 17 // Windows UWP calls Windows::Globalization directly, so this isn't needed there. 18 #if U_PLATFORM_USES_ONLY_WIN32_API && (U_PLATFORM_HAS_WINUWP_API == 0) 19 20 #include "wintz.h" 21 #include "cmemory.h" 22 #include "cstring.h" 23 24 #include "unicode/ures.h" 25 #include "unicode/ustring.h" 26 27 #ifndef WIN32_LEAN_AND_MEAN 28 # define WIN32_LEAN_AND_MEAN 29 #endif 30 # define VC_EXTRALEAN 31 # define NOUSER 32 # define NOSERVICE 33 # define NOIME 34 # define NOMCX 35 #include <windows.h> 36 37 #define MAX_LENGTH_ID 40 38 39 /* The layout of the Tzi value in the registry */ 40 typedef struct 41 { 42 int32_t bias; 43 int32_t standardBias; 44 int32_t daylightBias; 45 SYSTEMTIME standardDate; 46 SYSTEMTIME daylightDate; 47 } TZI; 48 49 /** 50 * Various registry keys and key fragments. 51 */ 52 static const char CURRENT_ZONE_REGKEY[] = "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation\\"; 53 static const char STANDARD_TIME_REGKEY[] = " Standard Time"; 54 static const char TZI_REGKEY[] = "TZI"; 55 static const char STD_REGKEY[] = "Std"; 56 57 /** 58 * The time zone root keys (under HKLM) for Win7+ 59 */ 60 static const char TZ_REGKEY[] = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\"; 61 62 static LONG openTZRegKey(HKEY *hkey, const char *winid) 63 { 64 char subKeyName[110]; /* TODO: why 110?? */ 65 char *name; 66 LONG result; 67 68 uprv_strcpy(subKeyName, TZ_REGKEY); 69 name = &subKeyName[strlen(subKeyName)]; 70 uprv_strcat(subKeyName, winid); 71 72 result = RegOpenKeyExA(HKEY_LOCAL_MACHINE, 73 subKeyName, 74 0, 75 KEY_QUERY_VALUE, 76 hkey); 77 return result; 78 } 79 80 static LONG getTZI(const char *winid, TZI *tzi) 81 { 82 DWORD cbData = sizeof(TZI); 83 LONG result; 84 HKEY hkey; 85 86 result = openTZRegKey(&hkey, winid); 87 88 if (result == ERROR_SUCCESS) 89 { 90 result = RegQueryValueExA(hkey, 91 TZI_REGKEY, 92 NULL, 93 NULL, 94 (LPBYTE)tzi, 95 &cbData); 96 RegCloseKey(hkey); 97 } 98 99 return result; 100 } 101 102 static LONG getSTDName(const char *winid, char *regStdName, int32_t length) 103 { 104 DWORD cbData = length; 105 LONG result; 106 HKEY hkey; 107 108 result = openTZRegKey(&hkey, winid); 109 110 if (result == ERROR_SUCCESS) 111 { 112 result = RegQueryValueExA(hkey, 113 STD_REGKEY, 114 NULL, 115 NULL, 116 (LPBYTE)regStdName, 117 &cbData); 118 RegCloseKey(hkey); 119 } 120 121 return result; 122 } 123 124 static LONG getTZKeyName(char* tzKeyName, int32_t length) 125 { 126 HKEY hkey; 127 LONG result = FALSE; 128 DWORD cbData = length; 129 130 if(ERROR_SUCCESS == RegOpenKeyExA( 131 HKEY_LOCAL_MACHINE, 132 CURRENT_ZONE_REGKEY, 133 0, 134 KEY_QUERY_VALUE, 135 &hkey)) 136 { 137 result = RegQueryValueExA( 138 hkey, 139 "TimeZoneKeyName", 140 NULL, 141 NULL, 142 (LPBYTE)tzKeyName, 143 &cbData); 144 145 RegCloseKey(hkey); 146 } 147 148 return result; 149 } 150 151 /* 152 This code attempts to detect the Windows time zone directly, 153 as set in the Windows Date and Time control panel. It attempts 154 to work on versions greater than Windows Vista and on localized 155 installs. It works by directly interrogating the registry and 156 comparing the data there with the data returned by the 157 GetTimeZoneInformation API, along with some other strategies. The 158 registry contains time zone data under this key: 159 160 HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\ 161 162 Under this key are several subkeys, one for each time zone. For 163 example these subkeys are named "Pacific Standard Time" on Vista+. 164 There are some other wrinkles; see the code for 165 details. The subkey name is NOT LOCALIZED, allowing us to support 166 localized installs. 167 168 Under the subkey are data values. We care about: 169 170 Std Standard time display name, localized 171 TZI Binary block of data 172 173 The TZI data is of particular interest. It contains the offset, two 174 more offsets for standard and daylight time, and the start and end 175 rules. This is the same data returned by the GetTimeZoneInformation 176 API. The API may modify the data on the way out, so we have to be 177 careful, but essentially we do a binary comparison against the TZI 178 blocks of various registry keys. When we find a match, we know what 179 time zone Windows is set to. Since the registry key is not 180 localized, we can then translate the key through a simple table 181 lookup into the corresponding ICU time zone. 182 183 This strategy doesn't always work because there are zones which 184 share an offset and rules, so more than one TZI block will match. 185 For example, both Tokyo and Seoul are at GMT+9 with no DST rules; 186 their TZI blocks are identical. For these cases, we fall back to a 187 name lookup. We attempt to match the display name as stored in the 188 registry for the current zone to the display name stored in the 189 registry for various Windows zones. By comparing the registry data 190 directly we avoid conversion complications. 191 192 Author: Alan Liu 193 Since: ICU 2.6 194 Based on original code by Carl Brown <cbrown (at) xnetinc.com> 195 */ 196 197 /** 198 * Main Windows time zone detection function. Returns the Windows 199 * time zone, translated to an ICU time zone, or NULL upon failure. 200 */ 201 U_CFUNC const char* U_EXPORT2 202 uprv_detectWindowsTimeZone() 203 { 204 UErrorCode status = U_ZERO_ERROR; 205 UResourceBundle* bundle = NULL; 206 char* icuid = NULL; 207 char apiStdName[MAX_LENGTH_ID]; 208 char regStdName[MAX_LENGTH_ID]; 209 char tmpid[MAX_LENGTH_ID]; 210 int32_t len; 211 int id; 212 int errorCode; 213 wchar_t ISOcodeW[3]; /* 2 letter iso code in UTF-16*/ 214 char ISOcodeA[3]; /* 2 letter iso code in ansi */ 215 216 LONG result; 217 TZI tziKey; 218 TZI tziReg; 219 TIME_ZONE_INFORMATION apiTZI; 220 221 BOOL tryPreVistaFallback; 222 OSVERSIONINFO osVerInfo; 223 224 /* Obtain TIME_ZONE_INFORMATION from the API, and then convert it 225 to TZI. We could also interrogate the registry directly; we do 226 this below if needed. */ 227 uprv_memset(&apiTZI, 0, sizeof(apiTZI)); 228 uprv_memset(&tziKey, 0, sizeof(tziKey)); 229 uprv_memset(&tziReg, 0, sizeof(tziReg)); 230 GetTimeZoneInformation(&apiTZI); 231 tziKey.bias = apiTZI.Bias; 232 uprv_memcpy((char *)&tziKey.standardDate, (char*)&apiTZI.StandardDate, 233 sizeof(apiTZI.StandardDate)); 234 uprv_memcpy((char *)&tziKey.daylightDate, (char*)&apiTZI.DaylightDate, 235 sizeof(apiTZI.DaylightDate)); 236 237 /* Convert the wchar_t* standard name to char* */ 238 uprv_memset(apiStdName, 0, sizeof(apiStdName)); 239 wcstombs(apiStdName, apiTZI.StandardName, MAX_LENGTH_ID); 240 241 tmpid[0] = 0; 242 243 id = GetUserGeoID(GEOCLASS_NATION); 244 errorCode = GetGeoInfoW(id, GEO_ISO2, ISOcodeW, 3, 0); 245 u_strToUTF8(ISOcodeA, 3, NULL, (const UChar *)ISOcodeW, 3, &status); 246 247 bundle = ures_openDirect(NULL, "windowsZones", &status); 248 ures_getByKey(bundle, "mapTimezones", bundle, &status); 249 250 /* 251 Windows Vista+ provides us with a "TimeZoneKeyName" that is not localized 252 and can be used to directly map a name in our bundle. Try to use that first 253 if we're on Vista or higher 254 */ 255 uprv_memset(&osVerInfo, 0, sizeof(osVerInfo)); 256 osVerInfo.dwOSVersionInfoSize = sizeof(osVerInfo); 257 tryPreVistaFallback = TRUE; 258 result = getTZKeyName(regStdName, sizeof(regStdName)); 259 if(ERROR_SUCCESS == result) 260 { 261 UResourceBundle* winTZ = ures_getByKey(bundle, regStdName, NULL, &status); 262 if(U_SUCCESS(status)) 263 { 264 const UChar* icuTZ = NULL; 265 if (errorCode != 0) 266 { 267 icuTZ = ures_getStringByKey(winTZ, ISOcodeA, &len, &status); 268 } 269 if (errorCode==0 || icuTZ==NULL) 270 { 271 /* fallback to default "001" and reset status */ 272 status = U_ZERO_ERROR; 273 icuTZ = ures_getStringByKey(winTZ, "001", &len, &status); 274 } 275 276 if(U_SUCCESS(status)) 277 { 278 int index=0; 279 while (! (*icuTZ == '\0' || *icuTZ ==' ')) 280 { 281 tmpid[index++]=(char)(*icuTZ++); /* safe to assume 'char' is ASCII compatible on windows */ 282 } 283 tmpid[index]='\0'; 284 tryPreVistaFallback = FALSE; 285 } 286 } 287 ures_close(winTZ); 288 } 289 290 if(tryPreVistaFallback) 291 { 292 /* Note: We get the winid not from static tables but from resource bundle. */ 293 while (U_SUCCESS(status) && ures_hasNext(bundle)) 294 { 295 UBool idFound = FALSE; 296 const char* winid; 297 UResourceBundle* winTZ = ures_getNextResource(bundle, NULL, &status); 298 if (U_FAILURE(status)) 299 { 300 break; 301 } 302 winid = ures_getKey(winTZ); 303 result = getTZI(winid, &tziReg); 304 305 if (result == ERROR_SUCCESS) 306 { 307 /* Windows alters the DaylightBias in some situations. 308 Using the bias and the rules suffices, so overwrite 309 these unreliable fields. */ 310 tziKey.standardBias = tziReg.standardBias; 311 tziKey.daylightBias = tziReg.daylightBias; 312 313 if (uprv_memcmp((char *)&tziKey, (char*)&tziReg, sizeof(tziKey)) == 0) 314 { 315 const UChar* icuTZ = NULL; 316 if (errorCode != 0) 317 { 318 icuTZ = ures_getStringByKey(winTZ, ISOcodeA, &len, &status); 319 } 320 if (errorCode==0 || icuTZ==NULL) 321 { 322 /* fallback to default "001" and reset status */ 323 status = U_ZERO_ERROR; 324 icuTZ = ures_getStringByKey(winTZ, "001", &len, &status); 325 } 326 327 if (U_SUCCESS(status)) 328 { 329 /* Get the standard name from the registry key to compare with 330 the one from Windows API call. */ 331 uprv_memset(regStdName, 0, sizeof(regStdName)); 332 result = getSTDName(winid, regStdName, sizeof(regStdName)); 333 if (result == ERROR_SUCCESS) 334 { 335 if (uprv_strcmp(apiStdName, regStdName) == 0) 336 { 337 idFound = TRUE; 338 } 339 } 340 341 /* tmpid buffer holds the ICU timezone ID corresponding to the timezone ID from Windows. 342 * If none is found, tmpid buffer will contain a fallback ID (i.e. the time zone ID matching 343 * the current time zone information) 344 */ 345 if (idFound || tmpid[0] == 0) 346 { 347 /* if icuTZ has more than one city, take only the first (i.e. terminate icuTZ at first space) */ 348 int index=0; 349 while (! (*icuTZ == '\0' || *icuTZ ==' ')) 350 { 351 tmpid[index++]=(char)(*icuTZ++); /* safe to assume 'char' is ASCII compatible on windows */ 352 } 353 tmpid[index]='\0'; 354 } 355 } 356 } 357 } 358 ures_close(winTZ); 359 if (idFound) 360 { 361 break; 362 } 363 } 364 } 365 366 /* 367 * Copy the timezone ID to icuid to be returned. 368 */ 369 if (tmpid[0] != 0) 370 { 371 len = uprv_strlen(tmpid); 372 icuid = (char*)uprv_calloc(len + 1, sizeof(char)); 373 if (icuid != NULL) 374 { 375 uprv_strcpy(icuid, tmpid); 376 } 377 } 378 379 ures_close(bundle); 380 381 return icuid; 382 } 383 384 #endif /* U_PLATFORM_USES_ONLY_WIN32_API && (U_PLATFORM_HAS_WINUWP_API == 0) */ 385