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