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 wchar_t CURRENT_ZONE_REGKEY[] = L"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
openTZRegKey(HKEY * hkey,const char * winid)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
getTZI(const char * winid,TZI * tzi)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
getSTDName(const char * winid,char * regStdName,int32_t length)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
getTZKeyName(char * tzKeyName,int32_t tzKeyNamelength)124 static LONG getTZKeyName(char* tzKeyName, int32_t tzKeyNamelength)
125 {
126 HKEY hkey;
127 LONG result = FALSE;
128 WCHAR timeZoneKeyNameData[128];
129 DWORD timeZoneKeyNameLength = static_cast<DWORD>(sizeof(timeZoneKeyNameData));
130
131 if(ERROR_SUCCESS == RegOpenKeyExW(
132 HKEY_LOCAL_MACHINE,
133 CURRENT_ZONE_REGKEY,
134 0,
135 KEY_QUERY_VALUE,
136 &hkey))
137 {
138 if (ERROR_SUCCESS == RegQueryValueExW(
139 hkey,
140 L"TimeZoneKeyName",
141 NULL,
142 NULL,
143 (LPBYTE)timeZoneKeyNameData,
144 &timeZoneKeyNameLength))
145 {
146 // Ensure null termination.
147 timeZoneKeyNameData[UPRV_LENGTHOF(timeZoneKeyNameData) - 1] = L'\0';
148
149 // Convert the UTF-16 string to UTF-8.
150 UErrorCode status = U_ZERO_ERROR;
151 u_strToUTF8(tzKeyName, tzKeyNamelength, NULL, reinterpret_cast<const UChar *>(timeZoneKeyNameData), -1, &status);
152 if (U_ZERO_ERROR == status)
153 {
154 result = ERROR_SUCCESS;
155 }
156 }
157 RegCloseKey(hkey);
158 }
159
160 return result;
161 }
162
163 /*
164 This code attempts to detect the Windows time zone directly,
165 as set in the Windows Date and Time control panel. It attempts
166 to work on versions greater than Windows Vista and on localized
167 installs. It works by directly interrogating the registry and
168 comparing the data there with the data returned by the
169 GetTimeZoneInformation API, along with some other strategies. The
170 registry contains time zone data under this key:
171
172 HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\
173
174 Under this key are several subkeys, one for each time zone. For
175 example these subkeys are named "Pacific Standard Time" on Vista+.
176 There are some other wrinkles; see the code for
177 details. The subkey name is NOT LOCALIZED, allowing us to support
178 localized installs.
179
180 Under the subkey are data values. We care about:
181
182 Std Standard time display name, localized
183 TZI Binary block of data
184
185 The TZI data is of particular interest. It contains the offset, two
186 more offsets for standard and daylight time, and the start and end
187 rules. This is the same data returned by the GetTimeZoneInformation
188 API. The API may modify the data on the way out, so we have to be
189 careful, but essentially we do a binary comparison against the TZI
190 blocks of various registry keys. When we find a match, we know what
191 time zone Windows is set to. Since the registry key is not
192 localized, we can then translate the key through a simple table
193 lookup into the corresponding ICU time zone.
194
195 This strategy doesn't always work because there are zones which
196 share an offset and rules, so more than one TZI block will match.
197 For example, both Tokyo and Seoul are at GMT+9 with no DST rules;
198 their TZI blocks are identical. For these cases, we fall back to a
199 name lookup. We attempt to match the display name as stored in the
200 registry for the current zone to the display name stored in the
201 registry for various Windows zones. By comparing the registry data
202 directly we avoid conversion complications.
203
204 Author: Alan Liu
205 Since: ICU 2.6
206 Based on original code by Carl Brown <cbrown@xnetinc.com>
207 */
208
209 /**
210 * Main Windows time zone detection function. Returns the Windows
211 * time zone, translated to an ICU time zone, or NULL upon failure.
212 */
213 U_CFUNC const char* U_EXPORT2
uprv_detectWindowsTimeZone()214 uprv_detectWindowsTimeZone()
215 {
216 UErrorCode status = U_ZERO_ERROR;
217 UResourceBundle* bundle = NULL;
218 char* icuid = NULL;
219 char apiStdName[MAX_LENGTH_ID];
220 char regStdName[MAX_LENGTH_ID];
221 char tmpid[MAX_LENGTH_ID];
222 int32_t len;
223 int id;
224 int errorCode;
225 wchar_t ISOcodeW[3]; /* 2 letter iso code in UTF-16*/
226 char ISOcodeA[3]; /* 2 letter iso code in ansi */
227
228 LONG result;
229 TZI tziKey;
230 TZI tziReg;
231 TIME_ZONE_INFORMATION apiTZI;
232
233 BOOL tryPreVistaFallback;
234 OSVERSIONINFO osVerInfo;
235
236 /* Obtain TIME_ZONE_INFORMATION from the API, and then convert it
237 to TZI. We could also interrogate the registry directly; we do
238 this below if needed. */
239 uprv_memset(&apiTZI, 0, sizeof(apiTZI));
240 uprv_memset(&tziKey, 0, sizeof(tziKey));
241 uprv_memset(&tziReg, 0, sizeof(tziReg));
242 GetTimeZoneInformation(&apiTZI);
243 tziKey.bias = apiTZI.Bias;
244 uprv_memcpy((char *)&tziKey.standardDate, (char*)&apiTZI.StandardDate,
245 sizeof(apiTZI.StandardDate));
246 uprv_memcpy((char *)&tziKey.daylightDate, (char*)&apiTZI.DaylightDate,
247 sizeof(apiTZI.DaylightDate));
248
249 /* Convert the wchar_t* standard name to char* */
250 uprv_memset(apiStdName, 0, sizeof(apiStdName));
251 wcstombs(apiStdName, apiTZI.StandardName, MAX_LENGTH_ID);
252
253 tmpid[0] = 0;
254
255 id = GetUserGeoID(GEOCLASS_NATION);
256 errorCode = GetGeoInfoW(id, GEO_ISO2, ISOcodeW, 3, 0);
257 u_strToUTF8(ISOcodeA, 3, NULL, (const UChar *)ISOcodeW, 3, &status);
258
259 bundle = ures_openDirect(NULL, "windowsZones", &status);
260 ures_getByKey(bundle, "mapTimezones", bundle, &status);
261
262 /*
263 Windows Vista+ provides us with a "TimeZoneKeyName" that is not localized
264 and can be used to directly map a name in our bundle. Try to use that first
265 if we're on Vista or higher
266 */
267 uprv_memset(&osVerInfo, 0, sizeof(osVerInfo));
268 osVerInfo.dwOSVersionInfoSize = sizeof(osVerInfo);
269 tryPreVistaFallback = TRUE;
270 result = getTZKeyName(regStdName, sizeof(regStdName));
271 if(ERROR_SUCCESS == result)
272 {
273 UResourceBundle* winTZ = ures_getByKey(bundle, regStdName, NULL, &status);
274 if(U_SUCCESS(status))
275 {
276 const UChar* icuTZ = NULL;
277 if (errorCode != 0)
278 {
279 icuTZ = ures_getStringByKey(winTZ, ISOcodeA, &len, &status);
280 }
281 if (errorCode==0 || icuTZ==NULL)
282 {
283 /* fallback to default "001" and reset status */
284 status = U_ZERO_ERROR;
285 icuTZ = ures_getStringByKey(winTZ, "001", &len, &status);
286 }
287
288 if(U_SUCCESS(status))
289 {
290 int index=0;
291 while (! (*icuTZ == '\0' || *icuTZ ==' '))
292 {
293 tmpid[index++]=(char)(*icuTZ++); /* safe to assume 'char' is ASCII compatible on windows */
294 }
295 tmpid[index]='\0';
296 tryPreVistaFallback = FALSE;
297 }
298 }
299 ures_close(winTZ);
300 }
301
302 if(tryPreVistaFallback)
303 {
304 /* Note: We get the winid not from static tables but from resource bundle. */
305 while (U_SUCCESS(status) && ures_hasNext(bundle))
306 {
307 UBool idFound = FALSE;
308 const char* winid;
309 UResourceBundle* winTZ = ures_getNextResource(bundle, NULL, &status);
310 if (U_FAILURE(status))
311 {
312 break;
313 }
314 winid = ures_getKey(winTZ);
315 result = getTZI(winid, &tziReg);
316
317 if (result == ERROR_SUCCESS)
318 {
319 /* Windows alters the DaylightBias in some situations.
320 Using the bias and the rules suffices, so overwrite
321 these unreliable fields. */
322 tziKey.standardBias = tziReg.standardBias;
323 tziKey.daylightBias = tziReg.daylightBias;
324
325 if (uprv_memcmp((char *)&tziKey, (char*)&tziReg, sizeof(tziKey)) == 0)
326 {
327 const UChar* icuTZ = NULL;
328 if (errorCode != 0)
329 {
330 icuTZ = ures_getStringByKey(winTZ, ISOcodeA, &len, &status);
331 }
332 if (errorCode==0 || icuTZ==NULL)
333 {
334 /* fallback to default "001" and reset status */
335 status = U_ZERO_ERROR;
336 icuTZ = ures_getStringByKey(winTZ, "001", &len, &status);
337 }
338
339 if (U_SUCCESS(status))
340 {
341 /* Get the standard name from the registry key to compare with
342 the one from Windows API call. */
343 uprv_memset(regStdName, 0, sizeof(regStdName));
344 result = getSTDName(winid, regStdName, sizeof(regStdName));
345 if (result == ERROR_SUCCESS)
346 {
347 if (uprv_strcmp(apiStdName, regStdName) == 0)
348 {
349 idFound = TRUE;
350 }
351 }
352
353 /* tmpid buffer holds the ICU timezone ID corresponding to the timezone ID from Windows.
354 * If none is found, tmpid buffer will contain a fallback ID (i.e. the time zone ID matching
355 * the current time zone information)
356 */
357 if (idFound || tmpid[0] == 0)
358 {
359 /* if icuTZ has more than one city, take only the first (i.e. terminate icuTZ at first space) */
360 int index=0;
361 while (! (*icuTZ == '\0' || *icuTZ ==' '))
362 {
363 tmpid[index++]=(char)(*icuTZ++); /* safe to assume 'char' is ASCII compatible on windows */
364 }
365 tmpid[index]='\0';
366 }
367 }
368 }
369 }
370 ures_close(winTZ);
371 if (idFound)
372 {
373 break;
374 }
375 }
376 }
377
378 /*
379 * Copy the timezone ID to icuid to be returned.
380 */
381 if (tmpid[0] != 0)
382 {
383 len = uprv_strlen(tmpid);
384 icuid = (char*)uprv_calloc(len + 1, sizeof(char));
385 if (icuid != NULL)
386 {
387 uprv_strcpy(icuid, tmpid);
388 }
389 }
390
391 ures_close(bundle);
392
393 return icuid;
394 }
395
396 #endif /* U_PLATFORM_USES_ONLY_WIN32_API && (U_PLATFORM_HAS_WINUWP_API == 0) */
397