|
1 /* |
|
2 ******************************************************************************** |
|
3 * Copyright (C) 2005-2013, International Business Machines |
|
4 * Corporation and others. All Rights Reserved. |
|
5 ******************************************************************************** |
|
6 * |
|
7 * File WINTZ.CPP |
|
8 * |
|
9 ******************************************************************************** |
|
10 */ |
|
11 |
|
12 #include "unicode/utypes.h" |
|
13 |
|
14 #if U_PLATFORM_HAS_WIN32_API |
|
15 |
|
16 #include "wintz.h" |
|
17 #include "cmemory.h" |
|
18 #include "cstring.h" |
|
19 |
|
20 #include "unicode/ustring.h" |
|
21 #include "unicode/ures.h" |
|
22 |
|
23 # define WIN32_LEAN_AND_MEAN |
|
24 # define VC_EXTRALEAN |
|
25 # define NOUSER |
|
26 # define NOSERVICE |
|
27 # define NOIME |
|
28 # define NOMCX |
|
29 #include <windows.h> |
|
30 |
|
31 #define MAX_LENGTH_ID 40 |
|
32 |
|
33 /* The layout of the Tzi value in the registry */ |
|
34 typedef struct |
|
35 { |
|
36 int32_t bias; |
|
37 int32_t standardBias; |
|
38 int32_t daylightBias; |
|
39 SYSTEMTIME standardDate; |
|
40 SYSTEMTIME daylightDate; |
|
41 } TZI; |
|
42 |
|
43 /** |
|
44 * Various registry keys and key fragments. |
|
45 */ |
|
46 static const char CURRENT_ZONE_REGKEY[] = "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation\\"; |
|
47 static const char STANDARD_NAME_REGKEY[] = "StandardName"; |
|
48 static const char STANDARD_TIME_REGKEY[] = " Standard Time"; |
|
49 static const char TZI_REGKEY[] = "TZI"; |
|
50 static const char STD_REGKEY[] = "Std"; |
|
51 |
|
52 /** |
|
53 * HKLM subkeys used to probe for the flavor of Windows. Note that we |
|
54 * specifically check for the "GMT" zone subkey; this is present on |
|
55 * NT, but on XP has become "GMT Standard Time". We need to |
|
56 * discriminate between these cases. |
|
57 */ |
|
58 static const char* const WIN_TYPE_PROBE_REGKEY[] = { |
|
59 /* WIN_9X_ME_TYPE */ |
|
60 "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones", |
|
61 |
|
62 /* WIN_NT_TYPE */ |
|
63 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\GMT" |
|
64 |
|
65 /* otherwise: WIN_2K_XP_TYPE */ |
|
66 }; |
|
67 |
|
68 /** |
|
69 * The time zone root subkeys (under HKLM) for different flavors of |
|
70 * Windows. |
|
71 */ |
|
72 static const char* const TZ_REGKEY[] = { |
|
73 /* WIN_9X_ME_TYPE */ |
|
74 "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones\\", |
|
75 |
|
76 /* WIN_NT_TYPE | WIN_2K_XP_TYPE */ |
|
77 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\" |
|
78 }; |
|
79 |
|
80 /** |
|
81 * Flavor of Windows, from our perspective. Not a real OS version, |
|
82 * but rather the flavor of the layout of the time zone information in |
|
83 * the registry. |
|
84 */ |
|
85 enum { |
|
86 WIN_9X_ME_TYPE = 1, |
|
87 WIN_NT_TYPE = 2, |
|
88 WIN_2K_XP_TYPE = 3 |
|
89 }; |
|
90 |
|
91 static int32_t gWinType = 0; |
|
92 |
|
93 static int32_t detectWindowsType() |
|
94 { |
|
95 int32_t winType; |
|
96 LONG result; |
|
97 HKEY hkey; |
|
98 |
|
99 /* Detect the version of windows by trying to open a sequence of |
|
100 probe keys. We don't use the OS version API because what we |
|
101 really want to know is how the registry is laid out. |
|
102 Specifically, is it 9x/Me or not, and is it "GMT" or "GMT |
|
103 Standard Time". */ |
|
104 for (winType = 0; winType < 2; winType++) { |
|
105 result = RegOpenKeyExA(HKEY_LOCAL_MACHINE, |
|
106 WIN_TYPE_PROBE_REGKEY[winType], |
|
107 0, |
|
108 KEY_QUERY_VALUE, |
|
109 &hkey); |
|
110 RegCloseKey(hkey); |
|
111 |
|
112 if (result == ERROR_SUCCESS) { |
|
113 break; |
|
114 } |
|
115 } |
|
116 |
|
117 return winType+1; /* +1 to bring it inline with the enum */ |
|
118 } |
|
119 |
|
120 static LONG openTZRegKey(HKEY *hkey, const char *winid) |
|
121 { |
|
122 char subKeyName[110]; /* TODO: why 96?? */ |
|
123 char *name; |
|
124 LONG result; |
|
125 |
|
126 /* This isn't thread safe, but it's good enough because the result should be constant per system. */ |
|
127 if (gWinType <= 0) { |
|
128 gWinType = detectWindowsType(); |
|
129 } |
|
130 |
|
131 uprv_strcpy(subKeyName, TZ_REGKEY[(gWinType != WIN_9X_ME_TYPE)]); |
|
132 name = &subKeyName[strlen(subKeyName)]; |
|
133 uprv_strcat(subKeyName, winid); |
|
134 |
|
135 if (gWinType == WIN_9X_ME_TYPE) { |
|
136 /* Remove " Standard Time" */ |
|
137 char *pStd = uprv_strstr(subKeyName, STANDARD_TIME_REGKEY); |
|
138 if (pStd) { |
|
139 *pStd = 0; |
|
140 } |
|
141 } |
|
142 |
|
143 result = RegOpenKeyExA(HKEY_LOCAL_MACHINE, |
|
144 subKeyName, |
|
145 0, |
|
146 KEY_QUERY_VALUE, |
|
147 hkey); |
|
148 return result; |
|
149 } |
|
150 |
|
151 static LONG getTZI(const char *winid, TZI *tzi) |
|
152 { |
|
153 DWORD cbData = sizeof(TZI); |
|
154 LONG result; |
|
155 HKEY hkey; |
|
156 |
|
157 result = openTZRegKey(&hkey, winid); |
|
158 |
|
159 if (result == ERROR_SUCCESS) { |
|
160 result = RegQueryValueExA(hkey, |
|
161 TZI_REGKEY, |
|
162 NULL, |
|
163 NULL, |
|
164 (LPBYTE)tzi, |
|
165 &cbData); |
|
166 |
|
167 } |
|
168 |
|
169 RegCloseKey(hkey); |
|
170 |
|
171 return result; |
|
172 } |
|
173 |
|
174 static LONG getSTDName(const char *winid, char *regStdName, int32_t length) { |
|
175 DWORD cbData = length; |
|
176 LONG result; |
|
177 HKEY hkey; |
|
178 |
|
179 result = openTZRegKey(&hkey, winid); |
|
180 |
|
181 if (result == ERROR_SUCCESS) { |
|
182 result = RegQueryValueExA(hkey, |
|
183 STD_REGKEY, |
|
184 NULL, |
|
185 NULL, |
|
186 (LPBYTE)regStdName, |
|
187 &cbData); |
|
188 |
|
189 } |
|
190 |
|
191 RegCloseKey(hkey); |
|
192 |
|
193 return result; |
|
194 } |
|
195 |
|
196 /* |
|
197 This code attempts to detect the Windows time zone, as set in the |
|
198 Windows Date and Time control panel. It attempts to work on |
|
199 multiple flavors of Windows (9x, Me, NT, 2000, XP) and on localized |
|
200 installs. It works by directly interrogating the registry and |
|
201 comparing the data there with the data returned by the |
|
202 GetTimeZoneInformation API, along with some other strategies. The |
|
203 registry contains time zone data under one of two keys (depending on |
|
204 the flavor of Windows): |
|
205 |
|
206 HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones\ |
|
207 HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\ |
|
208 |
|
209 Under this key are several subkeys, one for each time zone. These |
|
210 subkeys are named "Pacific" on Win9x/Me and "Pacific Standard Time" |
|
211 on WinNT/2k/XP. There are some other wrinkles; see the code for |
|
212 details. The subkey name is NOT LOCALIZED, allowing us to support |
|
213 localized installs. |
|
214 |
|
215 Under the subkey are data values. We care about: |
|
216 |
|
217 Std Standard time display name, localized |
|
218 TZI Binary block of data |
|
219 |
|
220 The TZI data is of particular interest. It contains the offset, two |
|
221 more offsets for standard and daylight time, and the start and end |
|
222 rules. This is the same data returned by the GetTimeZoneInformation |
|
223 API. The API may modify the data on the way out, so we have to be |
|
224 careful, but essentially we do a binary comparison against the TZI |
|
225 blocks of various registry keys. When we find a match, we know what |
|
226 time zone Windows is set to. Since the registry key is not |
|
227 localized, we can then translate the key through a simple table |
|
228 lookup into the corresponding ICU time zone. |
|
229 |
|
230 This strategy doesn't always work because there are zones which |
|
231 share an offset and rules, so more than one TZI block will match. |
|
232 For example, both Tokyo and Seoul are at GMT+9 with no DST rules; |
|
233 their TZI blocks are identical. For these cases, we fall back to a |
|
234 name lookup. We attempt to match the display name as stored in the |
|
235 registry for the current zone to the display name stored in the |
|
236 registry for various Windows zones. By comparing the registry data |
|
237 directly we avoid conversion complications. |
|
238 |
|
239 Author: Alan Liu |
|
240 Since: ICU 2.6 |
|
241 Based on original code by Carl Brown <cbrown@xnetinc.com> |
|
242 */ |
|
243 |
|
244 /** |
|
245 * Main Windows time zone detection function. Returns the Windows |
|
246 * time zone, translated to an ICU time zone, or NULL upon failure. |
|
247 */ |
|
248 U_CFUNC const char* U_EXPORT2 |
|
249 uprv_detectWindowsTimeZone() { |
|
250 UErrorCode status = U_ZERO_ERROR; |
|
251 UResourceBundle* bundle = NULL; |
|
252 char* icuid = NULL; |
|
253 UChar apiStd[MAX_LENGTH_ID]; |
|
254 char apiStdName[MAX_LENGTH_ID]; |
|
255 char regStdName[MAX_LENGTH_ID]; |
|
256 char tmpid[MAX_LENGTH_ID]; |
|
257 int32_t len; |
|
258 int id; |
|
259 int errorCode; |
|
260 char ISOcode[3]; /* 2 letter iso code */ |
|
261 |
|
262 LONG result; |
|
263 TZI tziKey; |
|
264 TZI tziReg; |
|
265 TIME_ZONE_INFORMATION apiTZI; |
|
266 |
|
267 /* Obtain TIME_ZONE_INFORMATION from the API, and then convert it |
|
268 to TZI. We could also interrogate the registry directly; we do |
|
269 this below if needed. */ |
|
270 uprv_memset(&apiTZI, 0, sizeof(apiTZI)); |
|
271 uprv_memset(&tziKey, 0, sizeof(tziKey)); |
|
272 uprv_memset(&tziReg, 0, sizeof(tziReg)); |
|
273 GetTimeZoneInformation(&apiTZI); |
|
274 tziKey.bias = apiTZI.Bias; |
|
275 uprv_memcpy((char *)&tziKey.standardDate, (char*)&apiTZI.StandardDate, |
|
276 sizeof(apiTZI.StandardDate)); |
|
277 uprv_memcpy((char *)&tziKey.daylightDate, (char*)&apiTZI.DaylightDate, |
|
278 sizeof(apiTZI.DaylightDate)); |
|
279 |
|
280 /* Convert the wchar_t* standard name to char* */ |
|
281 uprv_memset(apiStdName, 0, sizeof(apiStdName)); |
|
282 u_strFromWCS(apiStd, MAX_LENGTH_ID, NULL, apiTZI.StandardName, -1, &status); |
|
283 u_austrncpy(apiStdName, apiStd, sizeof(apiStdName) - 1); |
|
284 |
|
285 tmpid[0] = 0; |
|
286 |
|
287 id = GetUserGeoID(GEOCLASS_NATION); |
|
288 errorCode = GetGeoInfo(id,GEO_ISO2,ISOcode,3,0); |
|
289 |
|
290 bundle = ures_openDirect(NULL, "windowsZones", &status); |
|
291 ures_getByKey(bundle, "mapTimezones", bundle, &status); |
|
292 |
|
293 /* Note: We get the winid not from static tables but from resource bundle. */ |
|
294 while (U_SUCCESS(status) && ures_hasNext(bundle)) { |
|
295 UBool idFound = FALSE; |
|
296 const char* winid; |
|
297 UResourceBundle* winTZ = ures_getNextResource(bundle, NULL, &status); |
|
298 if (U_FAILURE(status)) { |
|
299 break; |
|
300 } |
|
301 winid = ures_getKey(winTZ); |
|
302 result = getTZI(winid, &tziReg); |
|
303 |
|
304 if (result == ERROR_SUCCESS) { |
|
305 /* Windows alters the DaylightBias in some situations. |
|
306 Using the bias and the rules suffices, so overwrite |
|
307 these unreliable fields. */ |
|
308 tziKey.standardBias = tziReg.standardBias; |
|
309 tziKey.daylightBias = tziReg.daylightBias; |
|
310 |
|
311 if (uprv_memcmp((char *)&tziKey, (char*)&tziReg, sizeof(tziKey)) == 0) { |
|
312 const UChar* icuTZ = NULL; |
|
313 if (errorCode != 0) { |
|
314 icuTZ = ures_getStringByKey(winTZ, ISOcode, &len, &status); |
|
315 } |
|
316 if (errorCode==0 || icuTZ==NULL) { |
|
317 /* fallback to default "001" and reset status */ |
|
318 status = U_ZERO_ERROR; |
|
319 icuTZ = ures_getStringByKey(winTZ, "001", &len, &status); |
|
320 } |
|
321 |
|
322 if (U_SUCCESS(status)) { |
|
323 /* Get the standard name from the registry key to compare with |
|
324 the one from Windows API call. */ |
|
325 uprv_memset(regStdName, 0, sizeof(regStdName)); |
|
326 result = getSTDName(winid, regStdName, sizeof(regStdName)); |
|
327 if (result == ERROR_SUCCESS) { |
|
328 if (uprv_strcmp(apiStdName, regStdName) == 0) { |
|
329 idFound = TRUE; |
|
330 } |
|
331 } |
|
332 |
|
333 /* tmpid buffer holds the ICU timezone ID corresponding to the timezone ID from Windows. |
|
334 * If none is found, tmpid buffer will contain a fallback ID (i.e. the time zone ID matching |
|
335 * the current time zone information) |
|
336 */ |
|
337 if (idFound || tmpid[0] == 0) { |
|
338 /* if icuTZ has more than one city, take only the first (i.e. terminate icuTZ at first space) */ |
|
339 int index=0; |
|
340 while (! (*icuTZ == '\0' || *icuTZ ==' ')) { |
|
341 tmpid[index++]=(char)(*icuTZ++); /* safe to assume 'char' is ASCII compatible on windows */ |
|
342 } |
|
343 tmpid[index]='\0'; |
|
344 } |
|
345 } |
|
346 } |
|
347 } |
|
348 ures_close(winTZ); |
|
349 if (idFound) { |
|
350 break; |
|
351 } |
|
352 } |
|
353 |
|
354 /* |
|
355 * Copy the timezone ID to icuid to be returned. |
|
356 */ |
|
357 if (tmpid[0] != 0) { |
|
358 len = uprv_strlen(tmpid); |
|
359 icuid = (char*)uprv_calloc(len + 1, sizeof(char)); |
|
360 if (icuid != NULL) { |
|
361 uprv_strcpy(icuid, tmpid); |
|
362 } |
|
363 } |
|
364 |
|
365 ures_close(bundle); |
|
366 |
|
367 return icuid; |
|
368 } |
|
369 |
|
370 #endif /* U_PLATFORM_HAS_WIN32_API */ |