1 /*
2 ********************************************************************************
3 * Copyright (C) 2005-2007, 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 #ifdef U_WINDOWS
15
16 #include "wintz.h"
17
18 #include "cmemory.h"
19 #include "cstring.h"
20
21 #include "unicode/ustring.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 ARRAY_SIZE(array) (sizeof array / sizeof array[0])
32 #define NEW_ARRAY(type,count) (type *) uprv_malloc((count) * sizeof(type))
33 #define DELETE_ARRAY(array) uprv_free((void *) (array))
34
35 #define ICUID_STACK_BUFFER_SIZE 32
36
37 /* The layout of the Tzi value in the registry */
38 typedef struct
39 {
40 int32_t bias;
41 int32_t standardBias;
42 int32_t daylightBias;
43 SYSTEMTIME standardDate;
44 SYSTEMTIME daylightDate;
45 } TZI;
46
47 typedef struct
48 {
49 const char *icuid;
50 const char *winid;
51 } WindowsICUMap;
52
53 typedef struct {
54 const char* winid;
55 const char* altwinid;
56 } WindowsZoneRemap;
57
58 /**
59 * Various registry keys and key fragments.
60 */
61 static const char CURRENT_ZONE_REGKEY[] = "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation\\";
62 static const char STANDARD_NAME_REGKEY[] = "StandardName";
63 static const char STANDARD_TIME_REGKEY[] = " Standard Time";
64 static const char TZI_REGKEY[] = "TZI";
65 static const char STD_REGKEY[] = "Std";
66
67 /**
68 * HKLM subkeys used to probe for the flavor of Windows. Note that we
69 * specifically check for the "GMT" zone subkey; this is present on
70 * NT, but on XP has become "GMT Standard Time". We need to
71 * discriminate between these cases.
72 */
73 static const char* const WIN_TYPE_PROBE_REGKEY[] = {
74 /* WIN_9X_ME_TYPE */
75 "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones",
76
77 /* WIN_NT_TYPE */
78 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\GMT"
79
80 /* otherwise: WIN_2K_XP_TYPE */
81 };
82
83 /**
84 * The time zone root subkeys (under HKLM) for different flavors of
85 * Windows.
86 */
87 static const char* const TZ_REGKEY[] = {
88 /* WIN_9X_ME_TYPE */
89 "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones\\",
90
91 /* WIN_NT_TYPE | WIN_2K_XP_TYPE */
92 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\"
93 };
94
95 /**
96 * Flavor of Windows, from our perspective. Not a real OS version,
97 * but rather the flavor of the layout of the time zone information in
98 * the registry.
99 */
100 enum {
101 WIN_9X_ME_TYPE = 0,
102 WIN_NT_TYPE = 1,
103 WIN_2K_XP_TYPE = 2
104 };
105
106 # if 0
107 /*
108 * ZONE_MAP from supplementalData.txt
109 */
110 static const WindowsICUMap NEW_ZONE_MAP[] = {
111 {"Africa/Cairo", "Egypt"},
112 {"Africa/Casablanca", "Greenwich"},
113 {"Africa/Johannesburg", "South Africa"},
114 {"Africa/Lagos", "W. Central Africa"},
115 {"Africa/Nairobi", "E. Africa"},
116 {"Africa/Windhoek", "Namibia"},
117 {"America/Anchorage", "Alaskan"},
118 {"America/Bogota", "SA Pacific"},
119 {"America/Buenos_Aires", "SA Eastern"},
120 {"America/Caracas", "SA Western"},
121 {"America/Chicago", "Central"},
122 {"America/Chihuahua", "Mountain Standard Time (Mexico)"},
123 {"America/Denver", "Mountain"},
124 {"America/Godthab", "Greenland"},
125 {"America/Guatemala", "Central America"},
126 {"America/Halifax", "Atlantic"},
127 {"America/Indianapolis", "US Eastern"},
128 {"America/Los_Angeles", "Pacific"},
129 {"America/Manaus", "Central Brazilian"},
130 {"America/Mexico_City", "Central Standard Time (Mexico)"},
131 {"America/Montevideo", "Montevideo"},
132 {"America/New_York", "Eastern"},
133 {"America/Noronha", "Mid-Atlantic"},
134 {"America/Phoenix", "US Mountain"},
135 {"America/Regina", "Canada Central"},
136 {"America/Santiago", "Pacific SA"},
137 {"America/Sao_Paulo", "E. South America"},
138 {"America/St_Johns", "Newfoundland"},
139 {"America/Tijuana", "Pacific Standard Time (Mexico)"},
140 {"Asia/Amman", "Jordan"},
141 {"Asia/Baghdad", "Arabic"},
142 {"Asia/Baku", "Azerbaijan"},
143 {"Asia/Bangkok", "SE Asia"},
144 {"Asia/Beirut", "Middle East"},
145 {"Asia/Calcutta", "India"},
146 {"Asia/Colombo", "Sri Lanka"},
147 {"Asia/Dhaka", "Central Asia"},
148 {"Asia/Jerusalem", "Israel"},
149 {"Asia/Kabul", "Afghanistan"},
150 {"Asia/Karachi", "West Asia"},
151 {"Asia/Katmandu", "Nepal"},
152 {"Asia/Krasnoyarsk", "North Asia"},
153 {"Asia/Muscat", "Arabian"},
154 {"Asia/Novosibirsk", "N. Central Asia"},
155 {"Asia/Rangoon", "Myanmar"},
156 {"Asia/Riyadh", "Arab"},
157 {"Asia/Seoul", "Korea"},
158 {"Asia/Shanghai", "China"},
159 {"Asia/Singapore", "Singapore"},
160 {"Asia/Taipei", "Taipei"},
161 {"Asia/Tbilisi", "Georgian"},
162 {"Asia/Tehran", "Iran"},
163 {"Asia/Tokyo", "Tokyo"},
164 {"Asia/Ulaanbaatar", "North Asia East"},
165 {"Asia/Vladivostok", "Vladivostok"},
166 {"Asia/Yakutsk", "Yakutsk"},
167 {"Asia/Yekaterinburg", "Ekaterinburg"},
168 {"Asia/Yerevan", "Caucasus"},
169 {"Atlantic/Azores", "Azores"},
170 {"Atlantic/Cape_Verde", "Cape Verde"},
171 {"Australia/Adelaide", "Cen. Australia"},
172 {"Australia/Brisbane", "E. Australia"},
173 {"Australia/Darwin", "AUS Central"},
174 {"Australia/Hobart", "Tasmania"},
175 {"Australia/Perth", "W. Australia"},
176 {"Australia/Sydney", "AUS Eastern"},
177 {"Europe/Berlin", "W. Europe"},
178 {"Europe/Helsinki", "FLE"},
179 {"Europe/Istanbul", "GTB"},
180 {"Europe/London", "GMT"},
181 {"Europe/Minsk", "E. Europe"},
182 {"Europe/Moscow", "Russian"},
183 {"Europe/Paris", "Romance"},
184 {"Europe/Prague", "Central Europe"},
185 {"Europe/Warsaw", "Central European"},
186 {"Pacific/Apia", "Samoa"},
187 {"Pacific/Auckland", "New Zealand"},
188 {"Pacific/Fiji", "Fiji"},
189 {"Pacific/Guadalcanal", "Central Pacific"},
190 {"Pacific/Guam", "West Pacific"},
191 {"Pacific/Honolulu", "Hawaiian"},
192 {"Pacific/Kwajalein", "Dateline"},
193 {"Pacific/Tongatapu", "Tonga"}
194 };
195 #endif
196
197 /* NOTE: Some Windows zone ids appear more than once. In such cases the
198 * ICU zone id from the first one is the preferred match.
199 */
200 static const WindowsICUMap ZONE_MAP[] = {
201 {"Pacific/Kwajalein", "Dateline"}, /* S (GMT-12:00) International Date Line West */
202 {"Etc/GMT+12", "Dateline"}, /* S (GMT-12:00) International Date Line West */
203
204 {"Pacific/Apia", "Samoa"}, /* S (GMT-11:00) Midway Island, Samoa */
205
206 {"Pacific/Honolulu", "Hawaiian"}, /* S (GMT-10:00) Hawaii */
207
208 {"America/Anchorage", "Alaskan"}, /* D (GMT-09:00) Alaska */
209
210 {"America/Los_Angeles", "Pacific"}, /* D (GMT-08:00) Pacific Time (US & Canada) */
211 {"America/Tijuana", "Pacific Standard Time (Mexico)"}, /* S (GMT-08:00) Tijuana, Baja California */
212
213 {"America/Phoenix", "US Mountain"}, /* S (GMT-07:00) Arizona */
214 {"America/Denver", "Mountain"}, /* D (GMT-07:00) Mountain Time (US & Canada) */
215 {"America/Chihuahua", "Mountain Standard Time (Mexico)"}, /* D (GMT-07:00) Chihuahua, La Paz, Mazatlan */
216
217 {"America/Managua", "Central America"}, /* S (GMT-06:00) Central America */ /* America/Guatemala? */
218 {"America/Regina", "Canada Central"}, /* S (GMT-06:00) Saskatchewan */
219 {"America/Mexico_City", "Central Standard Time (Mexico)"}, /* D (GMT-06:00) Guadalajara, Mexico City, Monterrey */
220 {"America/Chicago", "Central"}, /* D (GMT-06:00) Central Time (US & Canada) */
221
222 {"America/Indianapolis", "US Eastern"}, /* S (GMT-05:00) Indiana (East) */
223 {"America/Bogota", "SA Pacific"}, /* S (GMT-05:00) Bogota, Lima, Quito */
224 {"America/New_York", "Eastern"}, /* D (GMT-05:00) Eastern Time (US & Canada) */
225
226 {"America/Caracas", "SA Western"}, /* S (GMT-04:00) Caracas, La Paz */
227 {"America/Santiago", "Pacific SA"}, /* D (GMT-04:00) Santiago */
228 {"America/Halifax", "Atlantic"}, /* D (GMT-04:00) Atlantic Time (Canada) */
229 {"America/Manaus", "Central Brazilian"}, /* D (GMT-04:00 Manaus */
230
231 {"America/St_Johns", "Newfoundland"}, /* D (GMT-03:30) Newfoundland */
232
233 {"America/Buenos_Aires", "SA Eastern"}, /* S (GMT-03:00) Buenos Aires, Georgetown */
234 {"America/Godthab", "Greenland"}, /* D (GMT-03:00) Greenland */
235 {"America/Sao_Paulo", "E. South America"}, /* D (GMT-03:00) Brasilia */
236 {"America/Montevideo", "Montevideo"}, /* S (GMT-03:00) Montevideo */
237
238 {"America/Noronha", "Mid-Atlantic"}, /* D (GMT-02:00) Mid-Atlantic */
239
240 {"Atlantic/Cape_Verde", "Cape Verde"}, /* S (GMT-01:00) Cape Verde Is. */
241 {"Atlantic/Azores", "Azores"}, /* D (GMT-01:00) Azores */
242
243 {"Africa/Casablanca", "Greenwich"}, /* S (GMT) Casablanca, Monrovia */
244 {"Europe/London", "GMT"}, /* D (GMT) Greenwich Mean Time : Dublin, Edinburgh, Lisbon, London */
245
246 {"Africa/Lagos", "W. Central Africa"}, /* S (GMT+01:00) West Central Africa */
247 {"Europe/Berlin", "W. Europe"}, /* D (GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna */
248 {"Europe/Paris", "Romance"}, /* D (GMT+01:00) Brussels, Copenhagen, Madrid, Paris */
249 {"Eurpoe/Warsaw", "Central European"}, /* D (GMT+01:00) Sarajevo, Skopje, Warsaw, Zagreb */
250 {"Europe/Sarajevo", "Central European"}, /* D (GMT+01:00) Sarajevo, Skopje, Warsaw, Zagreb */
251 {"Europe/Prague", "Central Europe"}, /* D (GMT+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague */
252 {"Europe/Belgrade", "Central Europe"}, /* D (GMT+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague */
253
254 {"Africa/Johannesburg", "South Africa"}, /* S (GMT+02:00) Harare, Pretoria */
255 {"Asia/Jerusalem", "Israel"}, /* S (GMT+02:00) Jerusalem */
256 {"Europe/Istanbul", "GTB"}, /* D (GMT+02:00) Athens, Istanbul, Minsk */
257 {"Europe/Helsinki", "FLE"}, /* D (GMT+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius */
258 {"Africa/Cairo", "Egypt"}, /* D (GMT+02:00) Cairo */
259 {"Europe/Minsk", "E. Europe"}, /* D (GMT+02:00) Bucharest */
260 {"Europe/Bucharest", "E. Europe"}, /* D (GMT+02:00) Bucharest */
261 {"Africa/Windhoek", "Namibia"}, /* S (GMT+02:00) Windhoek */
262 {"Asia/Amman", "Jordan"}, /* S (GMT+02:00) Aman */
263 {"Asia/Beirut", "Middle East"}, /* S (GMT+02:00) Beirut */
264
265 {"Africa/Nairobi", "E. Africa"}, /* S (GMT+03:00) Nairobi */
266 {"Asia/Riyadh", "Arab"}, /* S (GMT+03:00) Kuwait, Riyadh */
267 {"Europe/Moscow", "Russian"}, /* D (GMT+03:00) Moscow, St. Petersburg, Volgograd */
268 {"Asia/Baghdad", "Arabic"}, /* D (GMT+03:00) Baghdad */
269
270 {"Asia/Tehran", "Iran"}, /* D (GMT+03:30) Tehran */
271
272 {"Asia/Muscat", "Arabian"}, /* S (GMT+04:00) Abu Dhabi, Muscat */
273 {"Asia/Tbilisi", "Georgian"}, /* D (GMT+04:00) Tbilisi */
274 {"Asia/Baku", "Azerbaijan"}, /* S (GMT+04:00) Baku */
275 {"Asia/Yerevan", "Caucasus"}, /* S (GMT+04:00) Yerevan */
276 {"Asia/Kabul", "Afghanistan"}, /* S (GMT+04:30) Kabul */
277
278 {"Asia/Karachi", "West Asia"}, /* S (GMT+05:00) Islamabad, Karachi, Tashkent */
279 {"Asia/Yekaterinburg", "Ekaterinburg"}, /* D (GMT+05:00) Ekaterinburg */
280
281 {"Asia/Calcutta", "India"}, /* S (GMT+05:30) Chennai, Kolkata, Mumbai, New Delhi */
282
283 {"Asia/Katmandu", "Nepal"}, /* S (GMT+05:45) Kathmandu */
284
285 {"Asia/Colombo", "Sri Lanka"}, /* S (GMT+06:00) Sri Jayawardenepura */
286 {"Asia/Dhaka", "Central Asia"}, /* S (GMT+06:00) Astana, Dhaka */
287 {"Asia/Novosibirsk", "N. Central Asia"}, /* D (GMT+06:00) Almaty, Novosibirsk */
288
289 {"Asia/Rangoon", "Myanmar"}, /* S (GMT+06:30) Rangoon */
290
291 {"Asia/Bangkok", "SE Asia"}, /* S (GMT+07:00) Bangkok, Hanoi, Jakarta */
292 {"Asia/Krasnoyarsk", "North Asia"}, /* D (GMT+07:00) Krasnoyarsk */
293
294 {"Australia/Perth", "W. Australia"}, /* S (GMT+08:00) Perth */
295 {"Asia/Taipei", "Taipei"}, /* S (GMT+08:00) Taipei */
296 {"Asia/Singapore", "Singapore"}, /* S (GMT+08:00) Kuala Lumpur, Singapore */
297 {"Asia/Shanghai", "China"}, /* S (GMT+08:00) Beijing, Chongqing, Hong Kong, Urumqi */
298 {"Asia/Hong_Kong", "China"}, /* S (GMT+08:00) Beijing, Chongqing, Hong Kong, Urumqi */
299 {"Asia/Ulaanbaatar", "North Asia East"}, /* D (GMT+08:00) Irkutsk, Ulaan Bataar */
300 {"Asia/Irkutsk", "North Asia East"}, /* D (GMT+08:00) Irkutsk, Ulaan Bataar */
301
302 {"Asia/Tokyo", "Tokyo"}, /* S (GMT+09:00) Osaka, Sapporo, Tokyo */
303 {"Asia/Seoul", "Korea"}, /* S (GMT+09:00) Seoul */
304 {"Asia/Yakutsk", "Yakutsk"}, /* D (GMT+09:00) Yakutsk */
305
306 {"Australia/Darwin", "AUS Central"}, /* S (GMT+09:30) Darwin */
307 {"Australia/Adelaide", "Cen. Australia"}, /* D (GMT+09:30) Adelaide */
308
309 {"Pacific/Guam", "West Pacific"}, /* S (GMT+10:00) Guam, Port Moresby */
310 {"Australia/Brisbane", "E. Australia"}, /* S (GMT+10:00) Brisbane */
311 {"Asia/Vladivostok", "Vladivostok"}, /* D (GMT+10:00) Vladivostok */
312 {"Australia/Hobart", "Tasmania"}, /* D (GMT+10:00) Hobart */
313 {"Australia/Sydney", "AUS Eastern"}, /* D (GMT+10:00) Canberra, Melbourne, Sydney */
314
315 {"Asia/Guadalcanal", "Central Pacific"}, /* S (GMT+11:00) Magadan, Solomon Is., New Caledonia */
316 {"Asia/Magadan", "Central Pacific"}, /* S (GMT+11:00) Magadan, Solomon Is., New Caledonia */
317
318 {"Pacific/Fiji", "Fiji"}, /* S (GMT+12:00) Fiji, Kamchatka, Marshall Is. */
319 {"Pacific/Auckland", "New Zealand"}, /* D (GMT+12:00) Auckland, Wellington */
320
321 {"Pacific/Tongatapu", "Tonga"}, /* S (GMT+13:00) Nuku'alofa */
322 NULL, NULL
323 };
324
325 /**
326 * If a lookup fails, we attempt to remap certain Windows ids to
327 * alternate Windows ids. If the alternate listed here begins with
328 * '-', we use it as is (without the '-'). If it begins with '+', we
329 * append a " Standard Time" if appropriate.
330 */
331 static const WindowsZoneRemap ZONE_REMAP[] = {
332 "Central European", "-Warsaw",
333 "Central Europe", "-Prague Bratislava",
334 "China", "-Beijing",
335
336 "Greenwich", "+GMT",
337 "GTB", "+GFT",
338 "Arab", "+Saudi Arabia",
339 "SE Asia", "+Bangkok",
340 "AUS Eastern", "+Sydney",
341 "Mountain Standard Time (Mexico)", "-Mexico Standard Time 2",
342 "Central Standard Time (Mexico)", "+Mexico",
343 NULL, NULL,
344 };
345
346 static int32_t fWinType = -1;
347
detectWindowsType()348 static int32_t detectWindowsType()
349 {
350 int32_t winType;
351 LONG result;
352 HKEY hkey;
353
354 /* Detect the version of windows by trying to open a sequence of
355 probe keys. We don't use the OS version API because what we
356 really want to know is how the registry is laid out.
357 Specifically, is it 9x/Me or not, and is it "GMT" or "GMT
358 Standard Time". */
359 for (winType = 0; winType < 2; winType += 1) {
360 result = RegOpenKeyExA(HKEY_LOCAL_MACHINE,
361 WIN_TYPE_PROBE_REGKEY[winType],
362 0,
363 KEY_QUERY_VALUE,
364 &hkey);
365 RegCloseKey(hkey);
366
367 if (result == ERROR_SUCCESS) {
368 break;
369 }
370 }
371
372 return winType;
373 }
374
375 /*
376 * TODO: Binary search sorted ZONE_MAP...
377 * (u_detectWindowsTimeZone() needs them sorted by offset...)
378 */
findWindowsZoneID(const UChar * icuid,int32_t length)379 static const char *findWindowsZoneID(const UChar *icuid, int32_t length)
380 {
381 char stackBuffer[ICUID_STACK_BUFFER_SIZE];
382 char *buffer = stackBuffer;
383 const char *result = NULL;
384 int i;
385
386 /*
387 * NOTE: >= because length doesn't include
388 * trailing null.
389 */
390 if (length >= ICUID_STACK_BUFFER_SIZE) {
391 buffer = NEW_ARRAY(char, length + 1);
392 }
393
394 u_UCharsToChars(icuid, buffer, length);
395 buffer[length] = '\0';
396
397 for (i = 0; ZONE_MAP[i].icuid != NULL; i += 1) {
398 if (uprv_strcmp(buffer, ZONE_MAP[i].icuid) == 0) {
399 result = ZONE_MAP[i].winid;
400 break;
401 }
402 }
403
404 if (buffer != stackBuffer) {
405 DELETE_ARRAY(buffer);
406 }
407
408 return result;
409 }
410
openTZRegKey(HKEY * hkey,const char * winid)411 static LONG openTZRegKey(HKEY *hkey, const char *winid)
412 {
413 char subKeyName[96]; /* TODO: why 96?? */
414 char *name;
415 LONG result;
416
417 /* TODO: This isn't thread safe, but it's probably good enough. */
418 if (fWinType < 0) {
419 fWinType = detectWindowsType();
420 }
421
422 uprv_strcpy(subKeyName, TZ_REGKEY[(fWinType == WIN_9X_ME_TYPE) ? 0 : 1]);
423 name = &subKeyName[strlen(subKeyName)];
424 uprv_strcat(subKeyName, winid);
425
426 if (fWinType != WIN_9X_ME_TYPE &&
427 (winid[strlen(winid) - 1] != '2') &&
428 (winid[strlen(winid) - 1] != ')') &&
429 !(fWinType == WIN_NT_TYPE && strcmp(winid, "GMT") == 0)) {
430 uprv_strcat(subKeyName, STANDARD_TIME_REGKEY);
431 }
432
433 result = RegOpenKeyExA(HKEY_LOCAL_MACHINE,
434 subKeyName,
435 0,
436 KEY_QUERY_VALUE,
437 hkey);
438
439 if (result != ERROR_SUCCESS) {
440 int i;
441
442 /* If the primary lookup fails, try to remap the Windows zone
443 ID, according to the remapping table. */
444 for (i=0; ZONE_REMAP[i].winid; i++) {
445 if (uprv_strcmp(winid, ZONE_REMAP[i].winid) == 0) {
446 uprv_strcpy(name, ZONE_REMAP[i].altwinid + 1);
447 if (*(ZONE_REMAP[i].altwinid) == '+' && fWinType != WIN_9X_ME_TYPE) {
448 uprv_strcat(subKeyName, STANDARD_TIME_REGKEY);
449 }
450 return RegOpenKeyExA(HKEY_LOCAL_MACHINE,
451 subKeyName,
452 0,
453 KEY_QUERY_VALUE,
454 hkey);
455 }
456 }
457 }
458
459 return result;
460 }
461
getTZI(const char * winid,TZI * tzi)462 static LONG getTZI(const char *winid, TZI *tzi)
463 {
464 DWORD cbData = sizeof(TZI);
465 LONG result;
466 HKEY hkey;
467
468 result = openTZRegKey(&hkey, winid);
469
470 if (result == ERROR_SUCCESS) {
471 result = RegQueryValueExA(hkey,
472 TZI_REGKEY,
473 NULL,
474 NULL,
475 (LPBYTE)tzi,
476 &cbData);
477
478 }
479
480 RegCloseKey(hkey);
481
482 return result;
483 }
484
485 U_CAPI UBool U_EXPORT2
uprv_getWindowsTimeZoneInfo(TIME_ZONE_INFORMATION * zoneInfo,const UChar * icuid,int32_t length)486 uprv_getWindowsTimeZoneInfo(TIME_ZONE_INFORMATION *zoneInfo, const UChar *icuid, int32_t length)
487 {
488 const char *winid;
489 TZI tzi;
490 LONG result;
491
492 winid = findWindowsZoneID(icuid, length);
493
494 if (winid != NULL) {
495 result = getTZI(winid, &tzi);
496
497 if (result == ERROR_SUCCESS) {
498 zoneInfo->Bias = tzi.bias;
499 zoneInfo->DaylightBias = tzi.daylightBias;
500 zoneInfo->StandardBias = tzi.standardBias;
501 zoneInfo->DaylightDate = tzi.daylightDate;
502 zoneInfo->StandardDate = tzi.standardDate;
503
504 return TRUE;
505 }
506 }
507
508 return FALSE;
509 }
510
511 /*
512 This code attempts to detect the Windows time zone, as set in the
513 Windows Date and Time control panel. It attempts to work on
514 multiple flavors of Windows (9x, Me, NT, 2000, XP) and on localized
515 installs. It works by directly interrogating the registry and
516 comparing the data there with the data returned by the
517 GetTimeZoneInformation API, along with some other strategies. The
518 registry contains time zone data under one of two keys (depending on
519 the flavor of Windows):
520
521 HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones\
522 HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\
523
524 Under this key are several subkeys, one for each time zone. These
525 subkeys are named "Pacific" on Win9x/Me and "Pacific Standard Time"
526 on WinNT/2k/XP. There are some other wrinkles; see the code for
527 details. The subkey name is NOT LOCALIZED, allowing us to support
528 localized installs.
529
530 Under the subkey are data values. We care about:
531
532 Std Standard time display name, localized
533 TZI Binary block of data
534
535 The TZI data is of particular interest. It contains the offset, two
536 more offsets for standard and daylight time, and the start and end
537 rules. This is the same data returned by the GetTimeZoneInformation
538 API. The API may modify the data on the way out, so we have to be
539 careful, but essentially we do a binary comparison against the TZI
540 blocks of various registry keys. When we find a match, we know what
541 time zone Windows is set to. Since the registry key is not
542 localized, we can then translate the key through a simple table
543 lookup into the corresponding ICU time zone.
544
545 This strategy doesn't always work because there are zones which
546 share an offset and rules, so more than one TZI block will match.
547 For example, both Tokyo and Seoul are at GMT+9 with no DST rules;
548 their TZI blocks are identical. For these cases, we fall back to a
549 name lookup. We attempt to match the display name as stored in the
550 registry for the current zone to the display name stored in the
551 registry for various Windows zones. By comparing the registry data
552 directly we avoid conversion complications.
553
554 Author: Alan Liu
555 Since: ICU 2.6
556 Based on original code by Carl Brown <cbrown@xnetinc.com>
557 */
558
559 /**
560 * Main Windows time zone detection function. Returns the Windows
561 * time zone, translated to an ICU time zone, or NULL upon failure.
562 */
563 U_CFUNC const char* U_EXPORT2
uprv_detectWindowsTimeZone()564 uprv_detectWindowsTimeZone() {
565 LONG result;
566 HKEY hkey;
567 TZI tziKey;
568 TZI tziReg;
569 TIME_ZONE_INFORMATION apiTZI;
570 int firstMatch, lastMatch;
571 int j;
572
573 /* Obtain TIME_ZONE_INFORMATION from the API, and then convert it
574 to TZI. We could also interrogate the registry directly; we do
575 this below if needed. */
576 uprv_memset(&apiTZI, 0, sizeof(apiTZI));
577 uprv_memset(&tziKey, 0, sizeof(tziKey));
578 uprv_memset(&tziReg, 0, sizeof(tziReg));
579 GetTimeZoneInformation(&apiTZI);
580 tziKey.bias = apiTZI.Bias;
581 uprv_memcpy((char *)&tziKey.standardDate, (char*)&apiTZI.StandardDate,
582 sizeof(apiTZI.StandardDate));
583 uprv_memcpy((char *)&tziKey.daylightDate, (char*)&apiTZI.DaylightDate,
584 sizeof(apiTZI.DaylightDate));
585
586 /* For each zone that can be identified by Offset+Rules, see if we
587 have a match. Continue scanning after finding a match,
588 recording the index of the first and the last match. We have
589 to do this because some zones are not unique under
590 Offset+Rules. */
591 firstMatch = -1;
592 lastMatch = -1;
593 for (j=0; ZONE_MAP[j].icuid; j++) {
594 result = getTZI(ZONE_MAP[j].winid, &tziReg);
595
596 if (result == ERROR_SUCCESS) {
597 /* Assume that offsets are grouped together, and bail out
598 when we've scanned everything with a matching
599 offset. */
600 if (firstMatch >= 0 && tziKey.bias != tziReg.bias) {
601 break;
602 }
603
604 /* Windows alters the DaylightBias in some situations.
605 Using the bias and the rules suffices, so overwrite
606 these unreliable fields. */
607 tziKey.standardBias = tziReg.standardBias;
608 tziKey.daylightBias = tziReg.daylightBias;
609
610 if (uprv_memcmp((char *)&tziKey, (char*)&tziReg, sizeof(tziKey)) == 0) {
611 if (firstMatch < 0) {
612 firstMatch = j;
613 }
614
615 lastMatch = j;
616 }
617 }
618 }
619
620 /* This should never happen; if it does it means our table doesn't
621 match Windows AT ALL, perhaps because this is post-XP? */
622 if (firstMatch < 0) {
623 return NULL;
624 }
625
626 if (firstMatch != lastMatch) {
627 char stdName[32];
628 DWORD stdNameSize;
629 char stdRegName[64];
630 DWORD stdRegNameSize;
631
632 /* Offset+Rules lookup yielded >= 2 matches. Try to match the
633 localized display name. Get the name from the registry
634 (not the API). This avoids conversion issues. Use the
635 standard name, since Windows modifies the daylight name to
636 match the standard name if there is no DST. */
637 if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,
638 CURRENT_ZONE_REGKEY,
639 0,
640 KEY_QUERY_VALUE,
641 &hkey) == ERROR_SUCCESS)
642 {
643 stdNameSize = sizeof(stdName);
644 result = RegQueryValueExA(hkey,
645 STANDARD_NAME_REGKEY,
646 NULL,
647 NULL,
648 (LPBYTE)stdName,
649 &stdNameSize);
650 RegCloseKey(hkey);
651
652 /*
653 * Scan through the Windows time zone data in the registry
654 * again (just the range of zones with matching TZIs) and
655 * look for a standard display name match.
656 */
657 for (j = firstMatch; j <= lastMatch; j += 1) {
658 stdRegNameSize = sizeof(stdRegName);
659 result = openTZRegKey(&hkey, ZONE_MAP[j].winid);
660
661 if (result == ERROR_SUCCESS) {
662 result = RegQueryValueExA(hkey,
663 STD_REGKEY,
664 NULL,
665 NULL,
666 (LPBYTE)stdRegName,
667 &stdRegNameSize);
668 }
669
670 RegCloseKey(hkey);
671
672 if (result == ERROR_SUCCESS &&
673 stdRegNameSize == stdNameSize &&
674 uprv_memcmp(stdName, stdRegName, stdNameSize) == 0)
675 {
676 firstMatch = j; /* record the match */
677 break;
678 }
679 }
680 } else {
681 RegCloseKey(hkey); /* should never get here */
682 }
683 }
684
685 return ZONE_MAP[firstMatch].icuid;
686 }
687
688 #endif /* #ifdef U_WINDOWS */
689