]>
Commit | Line | Data |
---|---|---|
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) */ |