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