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