2 ********************************************************************************
3 * Copyright (C) 2005-2006, 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
107 * TODO: Sort on ICU ID?
108 * TODO: This data should come from ICU/CLDR...
110 static const WindowsICUMap ZONE_MAP
[] = {
111 {"Etc/GMT+12", "Dateline"}, /* S (GMT-12:00) International Date Line West */
113 {"Pacific/Apia", "Samoa"}, /* S (GMT-11:00) Midway Island, Samoa */
115 {"Pacific/Honolulu", "Hawaiian"}, /* S (GMT-10:00) Hawaii */
117 {"America/Anchorage", "Alaskan"}, /* D (GMT-09:00) Alaska */
119 {"America/Los_Angeles", "Pacific"}, /* D (GMT-08:00) Pacific Time (US & Canada); Tijuana */
121 {"America/Phoenix", "US Mountain"}, /* S (GMT-07:00) Arizona */
122 {"America/Denver", "Mountain"}, /* D (GMT-07:00) Mountain Time (US & Canada) */
123 {"America/Chihuahua", "Mexico Standard Time 2"}, /* D (GMT-07:00) Chihuahua, La Paz, Mazatlan */
125 {"America/Managua", "Central America"}, /* S (GMT-06:00) Central America */
126 {"America/Regina", "Canada Central"}, /* S (GMT-06:00) Saskatchewan */
127 {"America/Mexico_City", "Mexico"}, /* D (GMT-06:00) Guadalajara, Mexico City, Monterrey */
128 {"America/Chicago", "Central"}, /* D (GMT-06:00) Central Time (US & Canada) */
130 {"America/Indianapolis", "US Eastern"}, /* S (GMT-05:00) Indiana (East) */
131 {"America/Bogota", "SA Pacific"}, /* S (GMT-05:00) Bogota, Lima, Quito */
132 {"America/New_York", "Eastern"}, /* D (GMT-05:00) Eastern Time (US & Canada) */
134 {"America/Caracas", "SA Western"}, /* S (GMT-04:00) Caracas, La Paz */
135 {"America/Santiago", "Pacific SA"}, /* D (GMT-04:00) Santiago */
136 {"America/Halifax", "Atlantic"}, /* D (GMT-04:00) Atlantic Time (Canada) */
138 {"America/St_Johns", "Newfoundland"}, /* D (GMT-03:30) Newfoundland */
140 {"America/Buenos_Aires", "SA Eastern"}, /* S (GMT-03:00) Buenos Aires, Georgetown */
141 {"America/Godthab", "Greenland"}, /* D (GMT-03:00) Greenland */
142 {"America/Sao_Paulo", "E. South America"}, /* D (GMT-03:00) Brasilia */
144 {"America/Noronha", "Mid-Atlantic"}, /* D (GMT-02:00) Mid-Atlantic */
146 {"Atlantic/Cape_Verde", "Cape Verde"}, /* S (GMT-01:00) Cape Verde Is. */
147 {"Atlantic/Azores", "Azores"}, /* D (GMT-01:00) Azores */
149 {"Africa/Casablanca", "Greenwich"}, /* S (GMT) Casablanca, Monrovia */
150 {"Europe/London", "GMT"}, /* D (GMT) Greenwich Mean Time : Dublin, Edinburgh, Lisbon, London */
152 {"Africa/Lagos", "W. Central Africa"}, /* S (GMT+01:00) West Central Africa */
153 {"Europe/Berlin", "W. Europe"}, /* D (GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna */
154 {"Europe/Paris", "Romance"}, /* D (GMT+01:00) Brussels, Copenhagen, Madrid, Paris */
155 {"Europe/Sarajevo", "Central European"}, /* D (GMT+01:00) Sarajevo, Skopje, Warsaw, Zagreb */
156 {"Europe/Belgrade", "Central Europe"}, /* D (GMT+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague */
158 {"Africa/Johannesburg", "South Africa"}, /* S (GMT+02:00) Harare, Pretoria */
159 {"Asia/Jerusalem", "Israel"}, /* S (GMT+02:00) Jerusalem */
160 {"Europe/Istanbul", "GTB"}, /* D (GMT+02:00) Athens, Istanbul, Minsk */
161 {"Europe/Helsinki", "FLE"}, /* D (GMT+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius */
162 {"Africa/Cairo", "Egypt"}, /* D (GMT+02:00) Cairo */
163 {"Europe/Bucharest", "E. Europe"}, /* D (GMT+02:00) Bucharest */
165 {"Africa/Nairobi", "E. Africa"}, /* S (GMT+03:00) Nairobi */
166 {"Asia/Riyadh", "Arab"}, /* S (GMT+03:00) Kuwait, Riyadh */
167 {"Europe/Moscow", "Russian"}, /* D (GMT+03:00) Moscow, St. Petersburg, Volgograd */
168 {"Asia/Baghdad", "Arabic"}, /* D (GMT+03:00) Baghdad */
170 {"Asia/Tehran", "Iran"}, /* D (GMT+03:30) Tehran */
172 {"Asia/Muscat", "Arabian"}, /* S (GMT+04:00) Abu Dhabi, Muscat */
173 {"Asia/Tbilisi", "Caucasus"}, /* D (GMT+04:00) Baku, Tbilisi, Yerevan */
175 {"Asia/Kabul", "Afghanistan"}, /* S (GMT+04:30) Kabul */
177 {"Asia/Karachi", "West Asia"}, /* S (GMT+05:00) Islamabad, Karachi, Tashkent */
178 {"Asia/Yekaterinburg", "Ekaterinburg"}, /* D (GMT+05:00) Ekaterinburg */
180 {"Asia/Calcutta", "India"}, /* S (GMT+05:30) Chennai, Kolkata, Mumbai, New Delhi */
182 {"Asia/Katmandu", "Nepal"}, /* S (GMT+05:45) Kathmandu */
184 {"Asia/Colombo", "Sri Lanka"}, /* S (GMT+06:00) Sri Jayawardenepura */
185 {"Asia/Dhaka", "Central Asia"}, /* S (GMT+06:00) Astana, Dhaka */
186 {"Asia/Novosibirsk", "N. Central Asia"}, /* D (GMT+06:00) Almaty, Novosibirsk */
188 {"Asia/Rangoon", "Myanmar"}, /* S (GMT+06:30) Rangoon */
190 {"Asia/Bangkok", "SE Asia"}, /* S (GMT+07:00) Bangkok, Hanoi, Jakarta */
191 {"Asia/Krasnoyarsk", "North Asia"}, /* D (GMT+07:00) Krasnoyarsk */
193 {"Australia/Perth", "W. Australia"}, /* S (GMT+08:00) Perth */
194 {"Asia/Taipei", "Taipei"}, /* S (GMT+08:00) Taipei */
195 {"Asia/Singapore", "Singapore"}, /* S (GMT+08:00) Kuala Lumpur, Singapore */
196 {"Asia/Hong_Kong", "China"}, /* S (GMT+08:00) Beijing, Chongqing, Hong Kong, Urumqi */
197 {"Asia/Irkutsk", "North Asia East"}, /* D (GMT+08:00) Irkutsk, Ulaan Bataar */
199 {"Asia/Tokyo", "Tokyo"}, /* S (GMT+09:00) Osaka, Sapporo, Tokyo */
200 {"Asia/Seoul", "Korea"}, /* S (GMT+09:00) Seoul */
201 {"Asia/Yakutsk", "Yakutsk"}, /* D (GMT+09:00) Yakutsk */
203 {"Australia/Darwin", "AUS Central"}, /* S (GMT+09:30) Darwin */
204 {"Australia/Adelaide", "Cen. Australia"}, /* D (GMT+09:30) Adelaide */
206 {"Pacific/Guam", "West Pacific"}, /* S (GMT+10:00) Guam, Port Moresby */
207 {"Australia/Brisbane", "E. Australia"}, /* S (GMT+10:00) Brisbane */
208 {"Asia/Vladivostok", "Vladivostok"}, /* D (GMT+10:00) Vladivostok */
209 {"Australia/Hobart", "Tasmania"}, /* D (GMT+10:00) Hobart */
210 {"Australia/Sydney", "AUS Eastern"}, /* D (GMT+10:00) Canberra, Melbourne, Sydney */
212 {"Asia/Magadan", "Central Pacific"}, /* S (GMT+11:00) Magadan, Solomon Is., New Caledonia */
214 {"Pacific/Fiji", "Fiji"}, /* S (GMT+12:00) Fiji, Kamchatka, Marshall Is. */
215 {"Pacific/Auckland", "New Zealand"}, /* D (GMT+12:00) Auckland, Wellington */
217 {"Pacific/Tongatapu", "Tonga"}, /* S (GMT+13:00) Nuku'alofa */
222 * If a lookup fails, we attempt to remap certain Windows ids to
223 * alternate Windows ids. If the alternate listed here begins with
224 * '-', we use it as is (without the '-'). If it begins with '+', we
225 * append a " Standard Time" if appropriate.
227 static const WindowsZoneRemap ZONE_REMAP
[] = {
228 "Central European", "-Warsaw",
229 "Central Europe", "-Prague Bratislava",
234 "Arab", "+Saudi Arabia",
235 "SE Asia", "+Bangkok",
236 "AUS Eastern", "+Sydney",
240 static int32_t fWinType
= -1;
242 static int32_t detectWindowsType()
248 /* Detect the version of windows by trying to open a sequence of
249 probe keys. We don't use the OS version API because what we
250 really want to know is how the registry is laid out.
251 Specifically, is it 9x/Me or not, and is it "GMT" or "GMT
253 for (winType
= 0; winType
< 2; winType
+= 1) {
254 result
= RegOpenKeyEx(HKEY_LOCAL_MACHINE
,
255 WIN_TYPE_PROBE_REGKEY
[winType
],
261 if (result
== ERROR_SUCCESS
) {
270 * TODO: Binary search sorted ZONE_MAP...
271 * (u_detectWindowsTimeZone() needs them sorted by offset...)
273 static const char *findWindowsZoneID(const UChar
*icuid
, int32_t length
)
275 char stackBuffer
[ICUID_STACK_BUFFER_SIZE
];
276 char *buffer
= stackBuffer
;
277 const char *result
= NULL
;
281 * NOTE: >= because length doesn't include
284 if (length
>= ICUID_STACK_BUFFER_SIZE
) {
285 buffer
= NEW_ARRAY(char, length
+ 1);
288 u_UCharsToChars(icuid
, buffer
, length
);
289 buffer
[length
] = '\0';
291 for (i
= 0; ZONE_MAP
[i
].icuid
!= NULL
; i
+= 1) {
292 if (uprv_strcmp(buffer
, ZONE_MAP
[i
].icuid
) == 0) {
293 result
= ZONE_MAP
[i
].winid
;
298 if (buffer
!= stackBuffer
) {
299 DELETE_ARRAY(buffer
);
305 static LONG
openTZRegKey(HKEY
*hkey
, const char *winid
)
307 char subKeyName
[96]; /* TODO: why 96?? */
311 /* TODO: This isn't thread safe, but it's probably good enough. */
313 fWinType
= detectWindowsType();
316 uprv_strcpy(subKeyName
, TZ_REGKEY
[(fWinType
== WIN_9X_ME_TYPE
) ? 0 : 1]);
317 name
= &subKeyName
[strlen(subKeyName
)];
318 uprv_strcat(subKeyName
, winid
);
320 if (fWinType
!= WIN_9X_ME_TYPE
&&
321 (winid
[strlen(winid
) - 1] != '2') &&
322 !(fWinType
== WIN_NT_TYPE
&& strcmp(winid
, "GMT") == 0)) {
323 uprv_strcat(subKeyName
, STANDARD_TIME_REGKEY
);
326 result
= RegOpenKeyEx(HKEY_LOCAL_MACHINE
,
332 if (result
!= ERROR_SUCCESS
) {
335 /* If the primary lookup fails, try to remap the Windows zone
336 ID, according to the remapping table. */
337 for (i
=0; ZONE_REMAP
[i
].winid
; i
++) {
338 if (uprv_strcmp(winid
, ZONE_REMAP
[i
].winid
) == 0) {
339 uprv_strcpy(name
, ZONE_REMAP
[i
].altwinid
+ 1);
340 if (*(ZONE_REMAP
[i
].altwinid
) == '+' && fWinType
!= WIN_9X_ME_TYPE
) {
341 uprv_strcat(subKeyName
, STANDARD_TIME_REGKEY
);
343 return RegOpenKeyEx(HKEY_LOCAL_MACHINE
,
355 static LONG
getTZI(const char *winid
, TZI
*tzi
)
357 DWORD cbData
= sizeof(TZI
);
361 result
= openTZRegKey(&hkey
, winid
);
363 if (result
== ERROR_SUCCESS
) {
364 result
= RegQueryValueEx(hkey
,
378 U_CAPI UBool U_EXPORT2
379 uprv_getWindowsTimeZoneInfo(TIME_ZONE_INFORMATION
*zoneInfo
, const UChar
*icuid
, int32_t length
)
385 winid
= findWindowsZoneID(icuid
, length
);
388 result
= getTZI(winid
, &tzi
);
390 if (result
== ERROR_SUCCESS
) {
391 zoneInfo
->Bias
= tzi
.bias
;
392 zoneInfo
->DaylightBias
= tzi
.daylightBias
;
393 zoneInfo
->StandardBias
= tzi
.standardBias
;
394 zoneInfo
->DaylightDate
= tzi
.daylightDate
;
395 zoneInfo
->StandardDate
= tzi
.standardDate
;
405 This code attempts to detect the Windows time zone, as set in the
406 Windows Date and Time control panel. It attempts to work on
407 multiple flavors of Windows (9x, Me, NT, 2000, XP) and on localized
408 installs. It works by directly interrogating the registry and
409 comparing the data there with the data returned by the
410 GetTimeZoneInformation API, along with some other strategies. The
411 registry contains time zone data under one of two keys (depending on
412 the flavor of Windows):
414 HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones\
415 HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\
417 Under this key are several subkeys, one for each time zone. These
418 subkeys are named "Pacific" on Win9x/Me and "Pacific Standard Time"
419 on WinNT/2k/XP. There are some other wrinkles; see the code for
420 details. The subkey name is NOT LOCALIZED, allowing us to support
423 Under the subkey are data values. We care about:
425 Std Standard time display name, localized
426 TZI Binary block of data
428 The TZI data is of particular interest. It contains the offset, two
429 more offsets for standard and daylight time, and the start and end
430 rules. This is the same data returned by the GetTimeZoneInformation
431 API. The API may modify the data on the way out, so we have to be
432 careful, but essentially we do a binary comparison against the TZI
433 blocks of various registry keys. When we find a match, we know what
434 time zone Windows is set to. Since the registry key is not
435 localized, we can then translate the key through a simple table
436 lookup into the corresponding ICU time zone.
438 This strategy doesn't always work because there are zones which
439 share an offset and rules, so more than one TZI block will match.
440 For example, both Tokyo and Seoul are at GMT+9 with no DST rules;
441 their TZI blocks are identical. For these cases, we fall back to a
442 name lookup. We attempt to match the display name as stored in the
443 registry for the current zone to the display name stored in the
444 registry for various Windows zones. By comparing the registry data
445 directly we avoid conversion complications.
449 Based on original code by Carl Brown <cbrown@xnetinc.com>
453 * Main Windows time zone detection function. Returns the Windows
454 * time zone, translated to an ICU time zone, or NULL upon failure.
456 U_CAPI
const char* U_EXPORT2
457 uprv_detectWindowsTimeZone() {
462 TIME_ZONE_INFORMATION apiTZI
;
463 int firstMatch
, lastMatch
;
466 /* Obtain TIME_ZONE_INFORMATION from the API, and then convert it
467 to TZI. We could also interrogate the registry directly; we do
468 this below if needed. */
469 uprv_memset(&apiTZI
, 0, sizeof(apiTZI
));
470 uprv_memset(&tziKey
, 0, sizeof(tziKey
));
471 uprv_memset(&tziReg
, 0, sizeof(tziReg
));
472 GetTimeZoneInformation(&apiTZI
);
473 tziKey
.bias
= apiTZI
.Bias
;
474 uprv_memcpy((char *)&tziKey
.standardDate
, (char*)&apiTZI
.StandardDate
,
475 sizeof(apiTZI
.StandardDate
));
476 uprv_memcpy((char *)&tziKey
.daylightDate
, (char*)&apiTZI
.DaylightDate
,
477 sizeof(apiTZI
.DaylightDate
));
479 /* For each zone that can be identified by Offset+Rules, see if we
480 have a match. Continue scanning after finding a match,
481 recording the index of the first and the last match. We have
482 to do this because some zones are not unique under
486 for (j
=0; ZONE_MAP
[j
].icuid
; j
++) {
487 result
= getTZI(ZONE_MAP
[j
].winid
, &tziReg
);
489 if (result
== ERROR_SUCCESS
) {
490 /* Assume that offsets are grouped together, and bail out
491 when we've scanned everything with a matching
493 if (firstMatch
>= 0 && tziKey
.bias
!= tziReg
.bias
) {
497 /* Windows alters the DaylightBias in some situations.
498 Using the bias and the rules suffices, so overwrite
499 these unreliable fields. */
500 tziKey
.standardBias
= tziReg
.standardBias
;
501 tziKey
.daylightBias
= tziReg
.daylightBias
;
503 if (uprv_memcmp((char *)&tziKey
, (char*)&tziReg
, sizeof(tziKey
)) == 0) {
504 if (firstMatch
< 0) {
513 /* This should never happen; if it does it means our table doesn't
514 match Windows AT ALL, perhaps because this is post-XP? */
515 if (firstMatch
< 0) {
519 if (firstMatch
!= lastMatch
) {
523 DWORD stdRegNameSize
;
525 /* Offset+Rules lookup yielded >= 2 matches. Try to match the
526 localized display name. Get the name from the registry
527 (not the API). This avoids conversion issues. Use the
528 standard name, since Windows modifies the daylight name to
529 match the standard name if there is no DST. */
530 if (RegOpenKeyEx(HKEY_LOCAL_MACHINE
,
534 &hkey
) == ERROR_SUCCESS
)
536 stdNameSize
= sizeof(stdName
);
537 result
= RegQueryValueEx(hkey
,
538 (LPTSTR
)STANDARD_NAME_REGKEY
,
546 * Scan through the Windows time zone data in the registry
547 * again (just the range of zones with matching TZIs) and
548 * look for a standard display name match.
550 for (j
= firstMatch
; j
<= lastMatch
; j
+= 1) {
551 stdRegNameSize
= sizeof(stdRegName
);
552 result
= openTZRegKey(&hkey
, ZONE_MAP
[j
].winid
);
554 if (result
== ERROR_SUCCESS
) {
555 result
= RegQueryValueEx(hkey
,
565 if (result
== ERROR_SUCCESS
&&
566 stdRegNameSize
== stdNameSize
&&
567 uprv_memcmp(stdName
, stdRegName
, stdNameSize
) == 0)
569 firstMatch
= j
; /* record the match */
574 RegCloseKey(hkey
); /* should never get here */
578 return ZONE_MAP
[firstMatch
].icuid
;
581 #endif /* #ifdef U_WINDOWS */