]>
Commit | Line | Data |
---|---|---|
f3c0d7a5 A |
1 | // © 2016 and later: Unicode, Inc. and others. |
2 | // License & terms of use: http://www.unicode.org/copyright.html | |
73c04bcf A |
3 | /* |
4 | ******************************************************************************** | |
2ca993e8 | 5 | * Copyright (C) 2005-2015, International Business Machines |
73c04bcf A |
6 | * Corporation and others. All Rights Reserved. |
7 | ******************************************************************************** | |
8 | * | |
9 | * File WINTZ.CPP | |
10 | * | |
11 | ******************************************************************************** | |
12 | */ | |
13 | ||
14 | #include "unicode/utypes.h" | |
15 | ||
f3c0d7a5 A |
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) | |
73c04bcf A |
19 | |
20 | #include "wintz.h" | |
73c04bcf A |
21 | #include "cmemory.h" |
22 | #include "cstring.h" | |
23 | ||
729e4ab9 | 24 | #include "unicode/ures.h" |
b331163b | 25 | #include "unicode/ustring.h" |
73c04bcf | 26 | |
f3c0d7a5 | 27 | #ifndef WIN32_LEAN_AND_MEAN |
73c04bcf | 28 | # define WIN32_LEAN_AND_MEAN |
f3c0d7a5 | 29 | #endif |
73c04bcf A |
30 | # define VC_EXTRALEAN |
31 | # define NOUSER | |
32 | # define NOSERVICE | |
33 | # define NOIME | |
34 | # define NOMCX | |
35 | #include <windows.h> | |
36 | ||
51004dcb | 37 | #define MAX_LENGTH_ID 40 |
4388f060 | 38 | |
73c04bcf A |
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 | ||
73c04bcf A |
49 | /** |
50 | * Various registry keys and key fragments. | |
51 | */ | |
52 | static const char CURRENT_ZONE_REGKEY[] = "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation\\"; | |
73c04bcf A |
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 | /** | |
f3c0d7a5 | 58 | * The time zone root keys (under HKLM) for Win7+ |
73c04bcf | 59 | */ |
f3c0d7a5 | 60 | static const char TZ_REGKEY[] = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\"; |
73c04bcf | 61 | |
73c04bcf A |
62 | static LONG openTZRegKey(HKEY *hkey, const char *winid) |
63 | { | |
729e4ab9 A |
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. | |
51004dcb | 66 | * At its max point this was 111, but the code had 110. Make it 128 for some wiggle room. */ |
729e4ab9 | 67 | char subKeyName[128]; |
73c04bcf A |
68 | char *name; |
69 | LONG result; | |
70 | ||
f3c0d7a5 | 71 | uprv_strcpy(subKeyName, TZ_REGKEY); |
73c04bcf A |
72 | name = &subKeyName[strlen(subKeyName)]; |
73 | uprv_strcat(subKeyName, winid); | |
74 | ||
46f4442e | 75 | result = RegOpenKeyExA(HKEY_LOCAL_MACHINE, |
73c04bcf A |
76 | subKeyName, |
77 | 0, | |
78 | KEY_QUERY_VALUE, | |
79 | hkey); | |
73c04bcf A |
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 | ||
f3c0d7a5 A |
91 | if (result == ERROR_SUCCESS) |
92 | { | |
46f4442e | 93 | result = RegQueryValueExA(hkey, |
73c04bcf A |
94 | TZI_REGKEY, |
95 | NULL, | |
96 | NULL, | |
97 | (LPBYTE)tzi, | |
98 | &cbData); | |
f3c0d7a5 | 99 | RegCloseKey(hkey); |
73c04bcf A |
100 | } |
101 | ||
73c04bcf A |
102 | return result; |
103 | } | |
104 | ||
f3c0d7a5 A |
105 | static LONG getSTDName(const char *winid, char *regStdName, int32_t length) |
106 | { | |
4388f060 A |
107 | DWORD cbData = length; |
108 | LONG result; | |
109 | HKEY hkey; | |
110 | ||
111 | result = openTZRegKey(&hkey, winid); | |
112 | ||
f3c0d7a5 A |
113 | if (result == ERROR_SUCCESS) |
114 | { | |
4388f060 A |
115 | result = RegQueryValueExA(hkey, |
116 | STD_REGKEY, | |
117 | NULL, | |
118 | NULL, | |
119 | (LPBYTE)regStdName, | |
120 | &cbData); | |
f3c0d7a5 | 121 | RegCloseKey(hkey); |
4388f060 A |
122 | } |
123 | ||
4388f060 A |
124 | return result; |
125 | } | |
126 | ||
f3c0d7a5 A |
127 | static LONG getTZKeyName(char* tzKeyName, int32_t length) |
128 | { | |
b331163b A |
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); | |
f3c0d7a5 A |
147 | |
148 | RegCloseKey(hkey); | |
b331163b A |
149 | } |
150 | ||
151 | return result; | |
152 | } | |
153 | ||
73c04bcf | 154 | /* |
f3c0d7a5 A |
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 | |
73c04bcf A |
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 | |
f3c0d7a5 | 161 | registry contains time zone data under this key: |
73c04bcf | 162 | |
73c04bcf A |
163 | HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\ |
164 | ||
f3c0d7a5 A |
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 | |
73c04bcf A |
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 | */ | |
46f4442e | 204 | U_CFUNC const char* U_EXPORT2 |
f3c0d7a5 A |
205 | uprv_detectWindowsTimeZone() |
206 | { | |
729e4ab9 A |
207 | UErrorCode status = U_ZERO_ERROR; |
208 | UResourceBundle* bundle = NULL; | |
209 | char* icuid = NULL; | |
4388f060 | 210 | char apiStdName[MAX_LENGTH_ID]; |
51004dcb | 211 | char regStdName[MAX_LENGTH_ID]; |
4388f060 | 212 | char tmpid[MAX_LENGTH_ID]; |
4388f060 | 213 | int32_t len; |
51004dcb A |
214 | int id; |
215 | int errorCode; | |
f3c0d7a5 | 216 | wchar_t ISOcodeW[3]; /* 2 letter iso code in UTF-16*/ |
b331163b | 217 | char ISOcodeA[3]; /* 2 letter iso code in ansi */ |
729e4ab9 | 218 | |
73c04bcf | 219 | LONG result; |
73c04bcf A |
220 | TZI tziKey; |
221 | TZI tziReg; | |
222 | TIME_ZONE_INFORMATION apiTZI; | |
73c04bcf | 223 | |
b331163b A |
224 | BOOL tryPreVistaFallback; |
225 | OSVERSIONINFO osVerInfo; | |
226 | ||
73c04bcf A |
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 | ||
4388f060 A |
240 | /* Convert the wchar_t* standard name to char* */ |
241 | uprv_memset(apiStdName, 0, sizeof(apiStdName)); | |
57a6839d | 242 | wcstombs(apiStdName, apiTZI.StandardName, MAX_LENGTH_ID); |
4388f060 A |
243 | |
244 | tmpid[0] = 0; | |
245 | ||
51004dcb | 246 | id = GetUserGeoID(GEOCLASS_NATION); |
f3c0d7a5 A |
247 | errorCode = GetGeoInfoW(id, GEO_ISO2, ISOcodeW, 3, 0); |
248 | u_strToUTF8(ISOcodeA, 3, NULL, (const UChar *)ISOcodeW, 3, &status); | |
51004dcb | 249 | |
729e4ab9 A |
250 | bundle = ures_openDirect(NULL, "windowsZones", &status); |
251 | ures_getByKey(bundle, "mapTimezones", bundle, &status); | |
73c04bcf | 252 | |
b331163b A |
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); | |
b331163b | 260 | tryPreVistaFallback = TRUE; |
f3c0d7a5 A |
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 | } | |
51004dcb | 278 | |
f3c0d7a5 A |
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 */ | |
b331163b | 285 | } |
f3c0d7a5 A |
286 | tmpid[index]='\0'; |
287 | tryPreVistaFallback = FALSE; | |
b331163b A |
288 | } |
289 | } | |
f3c0d7a5 | 290 | ures_close(winTZ); |
b331163b A |
291 | } |
292 | ||
f3c0d7a5 A |
293 | if(tryPreVistaFallback) |
294 | { | |
b331163b | 295 | /* Note: We get the winid not from static tables but from resource bundle. */ |
f3c0d7a5 A |
296 | while (U_SUCCESS(status) && ures_hasNext(bundle)) |
297 | { | |
b331163b A |
298 | UBool idFound = FALSE; |
299 | const char* winid; | |
300 | UResourceBundle* winTZ = ures_getNextResource(bundle, NULL, &status); | |
f3c0d7a5 A |
301 | if (U_FAILURE(status)) |
302 | { | |
b331163b A |
303 | break; |
304 | } | |
305 | winid = ures_getKey(winTZ); | |
306 | result = getTZI(winid, &tziReg); | |
307 | ||
f3c0d7a5 A |
308 | if (result == ERROR_SUCCESS) |
309 | { | |
b331163b A |
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 | ||
f3c0d7a5 A |
316 | if (uprv_memcmp((char *)&tziKey, (char*)&tziReg, sizeof(tziKey)) == 0) |
317 | { | |
b331163b | 318 | const UChar* icuTZ = NULL; |
f3c0d7a5 A |
319 | if (errorCode != 0) |
320 | { | |
b331163b A |
321 | icuTZ = ures_getStringByKey(winTZ, ISOcodeA, &len, &status); |
322 | } | |
f3c0d7a5 A |
323 | if (errorCode==0 || icuTZ==NULL) |
324 | { | |
b331163b A |
325 | /* fallback to default "001" and reset status */ |
326 | status = U_ZERO_ERROR; | |
327 | icuTZ = ures_getStringByKey(winTZ, "001", &len, &status); | |
328 | } | |
329 | ||
f3c0d7a5 A |
330 | if (U_SUCCESS(status)) |
331 | { | |
b331163b A |
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)); | |
f3c0d7a5 A |
336 | if (result == ERROR_SUCCESS) |
337 | { | |
338 | if (uprv_strcmp(apiStdName, regStdName) == 0) | |
339 | { | |
b331163b A |
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 | */ | |
f3c0d7a5 A |
348 | if (idFound || tmpid[0] == 0) |
349 | { | |
b331163b A |
350 | /* if icuTZ has more than one city, take only the first (i.e. terminate icuTZ at first space) */ |
351 | int index=0; | |
f3c0d7a5 A |
352 | while (! (*icuTZ == '\0' || *icuTZ ==' ')) |
353 | { | |
b331163b A |
354 | tmpid[index++]=(char)(*icuTZ++); /* safe to assume 'char' is ASCII compatible on windows */ |
355 | } | |
356 | tmpid[index]='\0'; | |
51004dcb | 357 | } |
4388f060 | 358 | } |
73c04bcf | 359 | } |
73c04bcf | 360 | } |
b331163b | 361 | ures_close(winTZ); |
f3c0d7a5 A |
362 | if (idFound) |
363 | { | |
b331163b A |
364 | break; |
365 | } | |
73c04bcf A |
366 | } |
367 | } | |
368 | ||
4388f060 A |
369 | /* |
370 | * Copy the timezone ID to icuid to be returned. | |
371 | */ | |
f3c0d7a5 A |
372 | if (tmpid[0] != 0) |
373 | { | |
4388f060 A |
374 | len = uprv_strlen(tmpid); |
375 | icuid = (char*)uprv_calloc(len + 1, sizeof(char)); | |
f3c0d7a5 A |
376 | if (icuid != NULL) |
377 | { | |
4388f060 A |
378 | uprv_strcpy(icuid, tmpid); |
379 | } | |
380 | } | |
381 | ||
729e4ab9 | 382 | ures_close(bundle); |
51004dcb | 383 | |
729e4ab9 | 384 | return icuid; |
73c04bcf A |
385 | } |
386 | ||
f3c0d7a5 | 387 | #endif /* U_PLATFORM_USES_ONLY_WIN32_API && (U_PLATFORM_HAS_WINUWP_API == 0) */ |