• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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