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