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