2 ********************************************************************************
3 * Copyright (C) 2005-2008, International Business Machines
4 * Corporation and others. All Rights Reserved.
5 ********************************************************************************
9 ********************************************************************************
12 #include "unicode/utypes.h"
21 #include "unicode/ustring.h"
23 # define WIN32_LEAN_AND_MEAN
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))
35 #define ICUID_STACK_BUFFER_SIZE 32
37 /* The layout of the Tzi value in the registry */
43 SYSTEMTIME standardDate
;
44 SYSTEMTIME daylightDate
;
59 * Various registry keys and key fragments.
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";
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.
73 static const char* const WIN_TYPE_PROBE_REGKEY
[] = {
75 "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones",
78 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\GMT"
80 /* otherwise: WIN_2K_XP_TYPE */
84 * The time zone root subkeys (under HKLM) for different flavors of
87 static const char* const TZ_REGKEY
[] = {
89 "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones\\",
91 /* WIN_NT_TYPE | WIN_2K_XP_TYPE */
92 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\"
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
108 * ZONE_MAP from supplementalData.txt
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"}
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.
200 static const WindowsICUMap ZONE_MAP
[] = {
201 /* S (GMT-12:00) International Date Line West */
202 {"Etc/GMT+12", "Dateline"},
204 /* S (GMT-11:00) Midway Island, Samoa */
205 {"Pacific/Apia", "Samoa"},
206 {"Pacific/Midway", "Samoa"},
208 /* S (GMT-10:00) Hawaii */
209 {"Pacific/Honolulu", "Hawaiian"},
211 /* D (GMT-09:00) Alaska */
212 {"America/Anchorage", "Alaskan"},
213 {"America/Juneau", "Alaskan"},
214 {"America/Yakutat", "Alaskan"},
215 {"America/Nome", "Alaskan"},
217 /* D (GMT-08:00) Pacific Time (US & Canada) */
218 {"America/Los_Angeles", "Pacific"},
219 {"America/Dawson", "Pacific"},
220 {"America/Vancouver", "Pacific"},
221 {"America/Whitehorse", "Pacific"},
223 /* D (GMT-08:00) Tijuana, Baja California */
224 {"America/Tijuana", "Pacific Standard Time (Mexico)"},
226 /* D (GMT-07:00) Mountain Time (US & Canada) */
227 {"America/Denver", "Mountain"},
228 {"America/Boise", "Mountain"},
229 {"America/Cambridge_Bay", "Mountain"},
230 {"America/Edmonton", "Mountain"},
231 {"America/Inuvik", "Mountain"},
232 {"America/Shiprock", "Mountain"},
233 {"America/Yellowknife", "Mountain"},
235 /* S (GMT-07:00) Arizona */
236 {"America/Phoenix", "US Mountain"},
238 /* D (GMT-07:00) Chihuahua, La Paz, Mazatlan */
239 {"America/Chihuahua", "Mountain Standard Time (Mexico)"},
240 {"America/Mazatlan", "Mountain Standard Time (Mexico)"},
242 /* D (GMT-06:00) Central Time (US & Canada) */
243 {"America/Chicago", "Central"},
244 {"America/Indiana/Knox", "Central"},
245 {"America/Indiana/Tell_City", "Central"},
246 {"America/Menominee", "Central"},
247 {"America/North_Dakota/Center", "Central"},
248 {"America/North_Dakota/New_Salem", "Central"},
249 {"America/Rainy_River", "Central"},
250 {"America/Rankin_Inlet", "Central"},
251 {"America/Winnipeg", "Central"},
253 /* D (GMT-06:00) Guadalajara, Mexico City, Monterrey */
254 {"America/Mexico_City", "Central Standard Time (Mexico)"},
255 {"America/Monterrey", "Central Standard Time (Mexico)"},
257 /* S (GMT-06:00) Central America */
258 {"America/Guatemala", "Central America"},
259 {"America/Belize", "Central America"},
260 {"America/Costa_Rica", "Central America"},
261 {"America/El_Salvador", "Central America"},
262 {"America/Managua", "Central America"},
263 {"America/Tegucigalpa", "Central America"},
264 {"Pacific/Galapagos", "Central America"},
266 /* S (GMT-06:00) Saskatchewan */
267 {"America/Regina", "Canada Central"},
268 {"America/Swift_Current", "Canada Central"},
270 /* D (GMT-05:00) Eastern Time (US & Canada) */
271 {"America/New_York", "Eastern"},
272 {"America/Detroit", "Eastern"},
273 {"America/Grand_Turk", "Eastern"},
274 {"America/Indiana/Marengo", "Eastern"},
275 {"America/Indiana/Petersburg", "Eastern"},
276 {"America/Indiana/Vevay", "Eastern"},
277 {"America/Indiana/Vincennes", "Eastern"},
278 {"America/Indiana/Winamac", "Eastern"},
279 {"America/Indianapolis", "Eastern"},
280 {"America/Iqaluit", "Eastern"},
281 {"America/Kentucky/Monticello", "Eastern"},
282 {"America/Louisville", "Eastern"},
283 {"America/Montreal", "Eastern"},
284 {"America/Nassau", "Eastern"},
285 {"America/Nipigon", "Eastern"},
286 {"America/Pangnirtung", "Eastern"},
287 {"America/Thunder_Bay", "Eastern"},
288 {"America/Toronto", "Eastern"},
290 /* S (GMT-05:00) Bogota, Lima, Quito, Rio Branco */
291 {"America/Bogota", "SA Pacific"},
292 {"America/Lima", "SA Pacific"},
293 {"America/Guayaquil", "SA Pacific"},
294 {"America/Rio_Branco", "SA Pacific"},
296 /* S (GMT-05:00) Indiana (East) */
297 {"Etc/GMT+5", "US Eastern"},
299 /* S (GMT-04:30) Caracas */
300 {"America/Caracas", "Venezuela"},
302 /* D (GMT-04:00) Atlantic Time (Canada) */
303 {"America/Halifax", "Atlantic"},
304 {"America/Glace_Bay", "Atlantic"},
305 {"America/Moncton", "Atlantic"},
307 /* D (GMT-04:00) Santiago */
308 {"America/Santiago", "Pacific SA"},
310 /* D (GMT-04:00) Manaus */ /* MS bug - DST is not used */
311 {"America/Manaus", "Central Brazilian"},
313 /* S (GMT-04:00) La Paz */
314 {"America/La_Paz", "SA Western"},
316 /* D (GMT-03:30) Newfoundland */
317 {"America/St_Johns", "Newfoundland"},
319 /* D (GMT-03:00) Brasilia */
320 {"America/Sao_Paulo", "E. South America"},
322 /* D (GMT-03:00) Buenos Aires */
323 {"America/Buenos_Aires", "Argentina"},
325 /* D (GMT-03:00) Greenland */
326 {"America/Godthab", "Greenland"},
328 /* D (GMT-03:00) Montevideo */
329 {"America/Montevideo", "Montevideo"},
331 /* S (GMT-03:00) Georgetown */ /* MS bug - Georgetown uses GMT-04:00 */
332 {"Etc/GMT+3", "SA Eastern"},
334 /* D (GMT-02:00) Mid-Atlantic */ /* MS bug - There is no such zone using GMT-02:00 with DST */
335 {"America/South_Georgia", "Mid-Atlantic"},
336 {"America/Noronha", "Mid-Atlantic"},
338 /* D (GMT-01:00) Azores */
339 {"Atlantic/Azores", "Azores"},
341 /* S (GMT-01:00) Cape Verde Is. */
342 {"Atlantic/Cape_Verde", "Cape Verde"},
344 /* D (GMT) Greenwich Mean Time : Dublin, Edinburgh, Lisbon, London */
345 {"Europe/London", "GMT"},
346 {"Europe/Dublin", "GMT"},
347 {"Europe/Lisbon", "GMT"},
349 /* S (GMT) Casablanca, Monrovia, Reykjavik */
350 {"Africa/Casablanca", "Greenwich"},
351 {"Africa/Monrovia", "Greenwich"},
352 {"Atlantic/Reykjavik", "Greenwich"},
354 /* D (GMT+01:00) Brussels, Copenhagen, Madrid, Paris */
355 {"Europe/Paris", "Romance"},
356 {"Europe/Brussels", "Romance"},
357 {"Europe/Copenhagen", "Romance"},
358 {"Europe/Madrid", "Romance"},
360 /* D (GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna */
361 {"Europe/Berlin", "W. Europe"},
362 {"Europe/Amsterdam", "W. Europe"},
363 {"Europe/Zurich", "W. Europe"},
364 {"Europe/Rome", "W. Europe"},
365 {"Europe/Stockholm", "W. Europe"},
366 {"Europe/Vienna", "W. Europe"},
368 /* D (GMT+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague */
369 {"Europe/Budapest", "Central Europe"},
370 {"Europe/Belgrade", "Central Europe"},
371 {"Europe/Bratislava", "Central Europe"},
372 {"Europe/Ljubljana", "Central Europe"},
373 {"Europe/Prague", "Central Europe"},
375 /* D (GMT+01:00) Sarajevo, Skopje, Warsaw, Zagreb */
376 {"Eurpoe/Warsaw", "Central European"},
377 {"Eurpoe/Sarajevo", "Central European"},
378 {"Eurpoe/Skopje", "Central European"},
379 {"Eurpoe/Zagreb", "Central European"},
381 /* S (GMT+01:00) West Central Africa */
382 {"Africa/Lagos", "W. Central Africa"},
383 {"Africa/Luanda", "W. Central Africa"},
384 {"Africa/Porto-Novo", "W. Central Africa"},
385 {"Africa/Douala", "W. Central Africa"},
386 {"Africa/Bangui", "W. Central Africa"},
387 {"Africa/Ndjamena", "W. Central Africa"},
388 {"Africa/Kinshasa", "W. Central Africa"},
389 {"Africa/Brazzaville", "W. Central Africa"},
390 {"Africa/Malabo", "W. Central Africa"},
391 {"Africa/Libreville", "W. Central Africa"},
392 {"Africa/Niamey", "W. Central Africa"},
394 /* D (GMT+02:00) Athens, Bucharest, Istanbul */
395 {"Europe/Istanbul", "GTB"},
396 {"Europe/Athens", "GTB"},
397 {"Europe/Bucharest", "GTB"},
399 /* D (GMT+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius */
400 {"Europe/Kiev", "FLE"},
401 {"Europe/Helsinki", "FLE"},
402 {"Europe/Riga", "FLE"},
403 {"Europe/Sofia", "FLE"},
404 {"Europe/Tallinn", "FLE"},
405 {"Europe/Vilnius", "FLE"},
407 /* D (GMT+02:00) Jerusalem */
408 {"Asia/Jerusalem", "Israel"},
410 /* D (GMT+02:00) Minsk */
411 {"Europe/Minsk", "E. Europe"},
413 /* D (GMT+02:00) Cairo */
414 {"Africa/Cairo", "Egypt"},
416 /* D (GMT+02:00) Amman */
417 {"Asia/Amman", "Jordan"},
419 /* D (GMT+02:00) Beirut */
420 {"Asia/Beirut", "Middle East"},
422 /* D (GMT+02:00) Windhoek */
423 {"Africa/Windhoek", "Namibia"},
425 /* S (GMT+02:00) Harare, Pretoria */
426 {"Africa/Johannesburg", "South Africa"},
427 {"Africa/Harare", "South Africa"},
429 /* D (GMT+03:00) Moscow, St. Petersburg, Volgograd */
430 {"Europe/Moscow", "Russian"},
431 {"Europe/Volgograd", "Russian"},
433 /* D (GMT+03:00) Baghdad */
434 {"Asia/Baghdad", "Arabic"},
436 /* S (GMT+03:00) Kuwait, Riyadh */
437 {"Asia/Riyadh", "Arab"},
438 {"Asia/Kuwait", "Arab"},
440 /* S (GMT+03:00) Nairobi */
441 {"Africa/Nairobi", "E. Africa"},
443 /* S (GMT+03:00) Tbilisi */ /* MS bug - Tbilisi uses GMT+04:00 */
444 {"Etc/GMT-3", "Georgian"},
446 /* D (GMT+03:30) Tehran */
447 {"Asia/Tehran", "Iran"},
449 /* D (GMT+04:00) Yerevan */
450 {"Asia/Yerevan", "Armenian"},
452 /* D (GMT+04:00) Baku */
453 {"Asia/Baku", "Azerbaijan"},
455 /* S (GMT+04:00) Abu Dhabi, Muscat */
456 {"Asia/Dubai", "Arabian"},
457 {"Asia/Muscat", "Arabian"},
459 /* S (GMT+04:00) Caucasus Standard Time */
460 {"Asia/Tbilisi", "Caucasus"},
462 /* S (GMT+04:30) Kabul */
463 {"Asia/Kabul", "Afghanistan"},
465 /* D (GMT+05:00) Ekaterinburg */
466 {"Asia/Yekaterinburg", "Ekaterinburg"},
468 /* S (GMT+05:00) Islamabad, Karachi, Tashkent */
469 {"Asia/Karachi", "West Asia"},
470 {"Asia/Tashkent", "West Asia"},
472 /* S (GMT+05:30) Chennai, Kolkata, Mumbai, New Delhi */
473 {"Asia/Calcutta", "India"},
475 /* S (GMT+05:30) Sri Jayawardenepura */
476 {"Asia/Colombo", "Sri Lanka"},
478 /* S (GMT+05:45) Kathmandu */
479 {"Asia/Katmandu", "Nepal"},
481 /* D (GMT+06:00) Almaty, Novosibirsk */ /* No DST in Almaty */
482 {"Asia/Novosibirsk", "N. Central Asia"},
484 /* S (GMT+06:00) Astana, Dhaka */
485 {"Asia/Dhaka", "Central Asia"},
486 {"Asia/Almaty", "Central Asia"},
487 {"Asia/Qyzylorda", "Central Asia"},
489 /* S (GMT+06:30) Yangon (Rangoon) */
490 {"Asia/Rangoon", "Myanmar"},
492 /* D (GMT+07:00) Krasnoyarsk */
493 {"Asia/Krasnoyarsk", "North Asia"},
495 /* S (GMT+07:00) Bangkok, Hanoi, Jakarta */
496 {"Asia/Bangkok", "SE Asia"},
497 {"Asia/Saigon", "SE Asia"},
498 {"Asia/Jakarta", "SE Asia"},
500 /* D (GMT+08:00) Irkutsk, Ulaan Bataar */ /* Ulaan Bataar does not use DST */
501 {"Asia/Irkutsk", "North Asia East"},
503 /* D (GMT+08:00) Perth */
504 {"Australia/Perth", "W. Australia"},
506 /* S (GMT+08:00) Beijing, Chongqing, Hong Kong, Urumqi */
507 {"Asia/Shanghai", "China"},
508 {"Asia/Chongqing", "China"},
509 {"Asia/Hong_Kong", "China"},
510 {"Asia/Urumqi", "China"},
512 /* S (GMT+08:00) Taipei */
513 {"Asia/Taipei", "Taipei"},
515 /* S (GMT+08:00) Kuala Lumpur, Singapore */
516 {"Asia/Singapore", "Singapore"},
517 {"Asia/Kuala_Lumpur", "Singapore"},
519 /* D (GMT+09:00) Yakutsk */
520 {"Asia/Yakutsk", "Yakutsk"},
522 /* S (GMT+09:00) Osaka, Sapporo, Tokyo */
523 {"Asia/Tokyo", "Tokyo"},
525 /* S (GMT+09:00) Seoul */
526 {"Asia/Seoul", "Korea"},
528 /* D (GMT+09:30) Adelaide */
529 {"Australia/Adelaide", "Cen. Australia"},
531 /* S (GMT+09:30) Darwin */
532 {"Australia/Darwin", "AUS Central"},
534 /* D (GMT+10:00) Canberra, Melbourne, Sydney */
535 {"Australia/Sydney", "AUS Eastern"},
536 {"Australia/Melbourne", "AUS Eastern"},
538 /* D (GMT+10:00) Hobart */
539 {"Australia/Hobart", "Tasmania"},
541 /* D (GMT+10:00) Vladivostok */
542 {"Asia/Vladivostok", "Vladivostok"},
544 /* S (GMT+10:00) Brisbane */
545 {"Australia/Brisbane", "E. Australia"},
547 /* S (GMT+10:00) Guam, Port Moresby */
548 {"Pacific/Port_Moresby", "West Pacific"},
549 {"Pacific/Guam", "West Pacific"},
551 /* S (GMT+11:00) Magadan, Solomon Is., New Caledonia */ /* Magadan uses DST */
552 {"Pacific/Guadalcanal", "Central Pacific"},
553 {"Pacific/Noumea", "Central Pacific"},
555 /* D (GMT+12:00) Auckland, Wellington */
556 {"Pacific/Auckland", "New Zealand"},
558 /* S (GMT+12:00) Fiji, Kamchatka, Marshall Is. */
559 {"Pacific/Fiji", "Fiji"},
560 {"Pacific/Majuro", "Fiji"},
561 {"Pacific/Kwajalein", "Fiji"},
563 /* S (GMT+13:00) Nuku'alofa */
564 {"Pacific/Tongatapu", "Tonga"},
570 * If a lookup fails, we attempt to remap certain Windows ids to
571 * alternate Windows ids. If the alternate listed here begins with
572 * '-', we use it as is (without the '-'). If it begins with '+', we
573 * append a " Standard Time" if appropriate.
575 static const WindowsZoneRemap ZONE_REMAP
[] = {
576 "Central European", "-Warsaw",
577 "Central Europe", "-Prague Bratislava",
582 "Arab", "+Saudi Arabia",
583 "SE Asia", "+Bangkok",
584 "AUS Eastern", "+Sydney",
585 "Mountain Standard Time (Mexico)", "-Mexico Standard Time 2",
586 "Central Standard Time (Mexico)", "+Mexico",
590 static int32_t gWinType
= 0;
592 static int32_t detectWindowsType()
598 /* Detect the version of windows by trying to open a sequence of
599 probe keys. We don't use the OS version API because what we
600 really want to know is how the registry is laid out.
601 Specifically, is it 9x/Me or not, and is it "GMT" or "GMT
603 for (winType
= 0; winType
< 2; winType
++) {
604 result
= RegOpenKeyExA(HKEY_LOCAL_MACHINE
,
605 WIN_TYPE_PROBE_REGKEY
[winType
],
611 if (result
== ERROR_SUCCESS
) {
616 return winType
+1; // +1 to bring it inline with the enum
620 * TODO: Binary search sorted ZONE_MAP...
621 * (u_detectWindowsTimeZone() needs them sorted by offset...)
623 static const char *findWindowsZoneID(const UChar
*icuid
, int32_t length
)
625 char stackBuffer
[ICUID_STACK_BUFFER_SIZE
];
626 char *buffer
= stackBuffer
;
627 const char *result
= NULL
;
631 * NOTE: >= because length doesn't include
634 if (length
>= ICUID_STACK_BUFFER_SIZE
) {
635 buffer
= NEW_ARRAY(char, length
+ 1);
638 u_UCharsToChars(icuid
, buffer
, length
);
639 buffer
[length
] = '\0';
641 for (i
= 0; ZONE_MAP
[i
].icuid
!= NULL
; i
+= 1) {
642 if (uprv_strcmp(buffer
, ZONE_MAP
[i
].icuid
) == 0) {
643 result
= ZONE_MAP
[i
].winid
;
648 if (buffer
!= stackBuffer
) {
649 DELETE_ARRAY(buffer
);
655 static LONG
openTZRegKey(HKEY
*hkey
, const char *winid
)
657 char subKeyName
[96]; /* TODO: why 96?? */
661 /* This isn't thread safe, but it's good enough because the result should be constant per system. */
663 gWinType
= detectWindowsType();
666 uprv_strcpy(subKeyName
, TZ_REGKEY
[(gWinType
!= WIN_9X_ME_TYPE
)]);
667 name
= &subKeyName
[strlen(subKeyName
)];
668 uprv_strcat(subKeyName
, winid
);
670 if (gWinType
!= WIN_9X_ME_TYPE
&&
671 (winid
[strlen(winid
) - 1] != '2') &&
672 (winid
[strlen(winid
) - 1] != ')') &&
673 !(gWinType
== WIN_NT_TYPE
&& strcmp(winid
, "GMT") == 0))
675 uprv_strcat(subKeyName
, STANDARD_TIME_REGKEY
);
678 result
= RegOpenKeyExA(HKEY_LOCAL_MACHINE
,
684 if (result
!= ERROR_SUCCESS
) {
687 /* If the primary lookup fails, try to remap the Windows zone
688 ID, according to the remapping table. */
689 for (i
=0; ZONE_REMAP
[i
].winid
; i
++) {
690 if (uprv_strcmp(winid
, ZONE_REMAP
[i
].winid
) == 0) {
691 uprv_strcpy(name
, ZONE_REMAP
[i
].altwinid
+ 1);
692 if (*(ZONE_REMAP
[i
].altwinid
) == '+' && gWinType
!= WIN_9X_ME_TYPE
) {
693 uprv_strcat(subKeyName
, STANDARD_TIME_REGKEY
);
695 return RegOpenKeyExA(HKEY_LOCAL_MACHINE
,
707 static LONG
getTZI(const char *winid
, TZI
*tzi
)
709 DWORD cbData
= sizeof(TZI
);
713 result
= openTZRegKey(&hkey
, winid
);
715 if (result
== ERROR_SUCCESS
) {
716 result
= RegQueryValueExA(hkey
,
730 U_CAPI UBool U_EXPORT2
731 uprv_getWindowsTimeZoneInfo(TIME_ZONE_INFORMATION
*zoneInfo
, const UChar
*icuid
, int32_t length
)
737 winid
= findWindowsZoneID(icuid
, length
);
740 result
= getTZI(winid
, &tzi
);
742 if (result
== ERROR_SUCCESS
) {
743 zoneInfo
->Bias
= tzi
.bias
;
744 zoneInfo
->DaylightBias
= tzi
.daylightBias
;
745 zoneInfo
->StandardBias
= tzi
.standardBias
;
746 zoneInfo
->DaylightDate
= tzi
.daylightDate
;
747 zoneInfo
->StandardDate
= tzi
.standardDate
;
757 This code attempts to detect the Windows time zone, as set in the
758 Windows Date and Time control panel. It attempts to work on
759 multiple flavors of Windows (9x, Me, NT, 2000, XP) and on localized
760 installs. It works by directly interrogating the registry and
761 comparing the data there with the data returned by the
762 GetTimeZoneInformation API, along with some other strategies. The
763 registry contains time zone data under one of two keys (depending on
764 the flavor of Windows):
766 HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones\
767 HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\
769 Under this key are several subkeys, one for each time zone. These
770 subkeys are named "Pacific" on Win9x/Me and "Pacific Standard Time"
771 on WinNT/2k/XP. There are some other wrinkles; see the code for
772 details. The subkey name is NOT LOCALIZED, allowing us to support
775 Under the subkey are data values. We care about:
777 Std Standard time display name, localized
778 TZI Binary block of data
780 The TZI data is of particular interest. It contains the offset, two
781 more offsets for standard and daylight time, and the start and end
782 rules. This is the same data returned by the GetTimeZoneInformation
783 API. The API may modify the data on the way out, so we have to be
784 careful, but essentially we do a binary comparison against the TZI
785 blocks of various registry keys. When we find a match, we know what
786 time zone Windows is set to. Since the registry key is not
787 localized, we can then translate the key through a simple table
788 lookup into the corresponding ICU time zone.
790 This strategy doesn't always work because there are zones which
791 share an offset and rules, so more than one TZI block will match.
792 For example, both Tokyo and Seoul are at GMT+9 with no DST rules;
793 their TZI blocks are identical. For these cases, we fall back to a
794 name lookup. We attempt to match the display name as stored in the
795 registry for the current zone to the display name stored in the
796 registry for various Windows zones. By comparing the registry data
797 directly we avoid conversion complications.
801 Based on original code by Carl Brown <cbrown@xnetinc.com>
805 * Main Windows time zone detection function. Returns the Windows
806 * time zone, translated to an ICU time zone, or NULL upon failure.
808 U_CFUNC
const char* U_EXPORT2
809 uprv_detectWindowsTimeZone() {
814 TIME_ZONE_INFORMATION apiTZI
;
815 int firstMatch
, lastMatch
;
818 /* Obtain TIME_ZONE_INFORMATION from the API, and then convert it
819 to TZI. We could also interrogate the registry directly; we do
820 this below if needed. */
821 uprv_memset(&apiTZI
, 0, sizeof(apiTZI
));
822 uprv_memset(&tziKey
, 0, sizeof(tziKey
));
823 uprv_memset(&tziReg
, 0, sizeof(tziReg
));
824 GetTimeZoneInformation(&apiTZI
);
825 tziKey
.bias
= apiTZI
.Bias
;
826 uprv_memcpy((char *)&tziKey
.standardDate
, (char*)&apiTZI
.StandardDate
,
827 sizeof(apiTZI
.StandardDate
));
828 uprv_memcpy((char *)&tziKey
.daylightDate
, (char*)&apiTZI
.DaylightDate
,
829 sizeof(apiTZI
.DaylightDate
));
831 /* For each zone that can be identified by Offset+Rules, see if we
832 have a match. Continue scanning after finding a match,
833 recording the index of the first and the last match. We have
834 to do this because some zones are not unique under
838 for (j
=0; ZONE_MAP
[j
].icuid
; j
++) {
839 result
= getTZI(ZONE_MAP
[j
].winid
, &tziReg
);
841 if (result
== ERROR_SUCCESS
) {
842 /* Assume that offsets are grouped together, and bail out
843 when we've scanned everything with a matching
845 if (firstMatch
>= 0 && tziKey
.bias
!= tziReg
.bias
) {
849 /* Windows alters the DaylightBias in some situations.
850 Using the bias and the rules suffices, so overwrite
851 these unreliable fields. */
852 tziKey
.standardBias
= tziReg
.standardBias
;
853 tziKey
.daylightBias
= tziReg
.daylightBias
;
855 if (uprv_memcmp((char *)&tziKey
, (char*)&tziReg
, sizeof(tziKey
)) == 0) {
856 if (firstMatch
< 0) {
865 /* This should never happen; if it does it means our table doesn't
866 match Windows AT ALL, perhaps because this is post-XP? */
867 if (firstMatch
< 0) {
871 if (firstMatch
!= lastMatch
) {
875 DWORD stdRegNameSize
;
877 /* Offset+Rules lookup yielded >= 2 matches. Try to match the
878 localized display name. Get the name from the registry
879 (not the API). This avoids conversion issues. Use the
880 standard name, since Windows modifies the daylight name to
881 match the standard name if there is no DST. */
882 if (RegOpenKeyExA(HKEY_LOCAL_MACHINE
,
886 &hkey
) == ERROR_SUCCESS
)
888 stdNameSize
= sizeof(stdName
);
889 result
= RegQueryValueExA(hkey
,
890 STANDARD_NAME_REGKEY
,
898 * Scan through the Windows time zone data in the registry
899 * again (just the range of zones with matching TZIs) and
900 * look for a standard display name match.
902 for (j
= firstMatch
; j
<= lastMatch
; j
+= 1) {
903 stdRegNameSize
= sizeof(stdRegName
);
904 result
= openTZRegKey(&hkey
, ZONE_MAP
[j
].winid
);
906 if (result
== ERROR_SUCCESS
) {
907 result
= RegQueryValueExA(hkey
,
917 if (result
== ERROR_SUCCESS
&&
918 stdRegNameSize
== stdNameSize
&&
919 uprv_memcmp(stdName
, stdRegName
, stdNameSize
) == 0)
921 firstMatch
= j
; /* record the match */
926 RegCloseKey(hkey
); /* should never get here */
930 return ZONE_MAP
[firstMatch
].icuid
;
933 #endif /* #ifdef U_WINDOWS */