• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2 ********************************************************************************
3 *   Copyright (C) 2005-2012, 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 #if U_PLATFORM_HAS_WIN32_API
15 
16 #include "wintz.h"
17 #include "cmemory.h"
18 #include "cstring.h"
19 
20 #include "unicode/ustring.h"
21 #include "unicode/ures.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 MAX_LENGTH_ID 40
32 
33 /* The layout of the Tzi value in the registry */
34 typedef struct
35 {
36     int32_t bias;
37     int32_t standardBias;
38     int32_t daylightBias;
39     SYSTEMTIME standardDate;
40     SYSTEMTIME daylightDate;
41 } TZI;
42 
43 /**
44  * Various registry keys and key fragments.
45  */
46 static const char CURRENT_ZONE_REGKEY[] = "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation\\";
47 static const char STANDARD_NAME_REGKEY[] = "StandardName";
48 static const char STANDARD_TIME_REGKEY[] = " Standard Time";
49 static const char TZI_REGKEY[] = "TZI";
50 static const char STD_REGKEY[] = "Std";
51 
52 /**
53  * HKLM subkeys used to probe for the flavor of Windows.  Note that we
54  * specifically check for the "GMT" zone subkey; this is present on
55  * NT, but on XP has become "GMT Standard Time".  We need to
56  * discriminate between these cases.
57  */
58 static const char* const WIN_TYPE_PROBE_REGKEY[] = {
59     /* WIN_9X_ME_TYPE */
60     "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones",
61 
62     /* WIN_NT_TYPE */
63     "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\GMT"
64 
65     /* otherwise: WIN_2K_XP_TYPE */
66 };
67 
68 /**
69  * The time zone root subkeys (under HKLM) for different flavors of
70  * Windows.
71  */
72 static const char* const TZ_REGKEY[] = {
73     /* WIN_9X_ME_TYPE */
74     "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones\\",
75 
76     /* WIN_NT_TYPE | WIN_2K_XP_TYPE */
77     "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\"
78 };
79 
80 /**
81  * Flavor of Windows, from our perspective.  Not a real OS version,
82  * but rather the flavor of the layout of the time zone information in
83  * the registry.
84  */
85 enum {
86     WIN_9X_ME_TYPE = 1,
87     WIN_NT_TYPE = 2,
88     WIN_2K_XP_TYPE = 3
89 };
90 
91 static int32_t gWinType = 0;
92 
detectWindowsType()93 static int32_t detectWindowsType()
94 {
95     int32_t winType;
96     LONG result;
97     HKEY hkey;
98 
99     /* Detect the version of windows by trying to open a sequence of
100         probe keys.  We don't use the OS version API because what we
101         really want to know is how the registry is laid out.
102         Specifically, is it 9x/Me or not, and is it "GMT" or "GMT
103         Standard Time". */
104     for (winType = 0; winType < 2; winType++) {
105         result = RegOpenKeyExA(HKEY_LOCAL_MACHINE,
106                               WIN_TYPE_PROBE_REGKEY[winType],
107                               0,
108                               KEY_QUERY_VALUE,
109                               &hkey);
110         RegCloseKey(hkey);
111 
112         if (result == ERROR_SUCCESS) {
113             break;
114         }
115     }
116 
117     return winType+1; /* +1 to bring it inline with the enum */
118 }
119 
openTZRegKey(HKEY * hkey,const char * winid)120 static LONG openTZRegKey(HKEY *hkey, const char *winid)
121 {
122     char subKeyName[110]; /* TODO: why 96?? */
123     char *name;
124     LONG result;
125 
126     /* This isn't thread safe, but it's good enough because the result should be constant per system. */
127     if (gWinType <= 0) {
128         gWinType = detectWindowsType();
129     }
130 
131     uprv_strcpy(subKeyName, TZ_REGKEY[(gWinType != WIN_9X_ME_TYPE)]);
132     name = &subKeyName[strlen(subKeyName)];
133     uprv_strcat(subKeyName, winid);
134 
135     if (gWinType == WIN_9X_ME_TYPE) {
136         /* Remove " Standard Time" */
137         char *pStd = uprv_strstr(subKeyName, STANDARD_TIME_REGKEY);
138         if (pStd) {
139             *pStd = 0;
140         }
141     }
142 
143     result = RegOpenKeyExA(HKEY_LOCAL_MACHINE,
144                             subKeyName,
145                             0,
146                             KEY_QUERY_VALUE,
147                             hkey);
148     return result;
149 }
150 
getTZI(const char * winid,TZI * tzi)151 static LONG getTZI(const char *winid, TZI *tzi)
152 {
153     DWORD cbData = sizeof(TZI);
154     LONG result;
155     HKEY hkey;
156 
157     result = openTZRegKey(&hkey, winid);
158 
159     if (result == ERROR_SUCCESS) {
160         result = RegQueryValueExA(hkey,
161                                     TZI_REGKEY,
162                                     NULL,
163                                     NULL,
164                                     (LPBYTE)tzi,
165                                     &cbData);
166 
167     }
168 
169     RegCloseKey(hkey);
170 
171     return result;
172 }
173 
getSTDName(const char * winid,char * regStdName,int32_t length)174 static LONG getSTDName(const char *winid, char *regStdName, int32_t length) {
175     DWORD cbData = length;
176     LONG result;
177     HKEY hkey;
178 
179     result = openTZRegKey(&hkey, winid);
180 
181     if (result == ERROR_SUCCESS) {
182         result = RegQueryValueExA(hkey,
183                                     STD_REGKEY,
184                                     NULL,
185                                     NULL,
186                                     (LPBYTE)regStdName,
187                                     &cbData);
188 
189     }
190 
191     RegCloseKey(hkey);
192 
193     return result;
194 }
195 
196 /*
197   This code attempts to detect the Windows time zone, as set in the
198   Windows Date and Time control panel.  It attempts to work on
199   multiple flavors of Windows (9x, Me, NT, 2000, XP) and on localized
200   installs.  It works by directly interrogating the registry and
201   comparing the data there with the data returned by the
202   GetTimeZoneInformation API, along with some other strategies.  The
203   registry contains time zone data under one of two keys (depending on
204   the flavor of Windows):
205 
206     HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones\
207     HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\
208 
209   Under this key are several subkeys, one for each time zone.  These
210   subkeys are named "Pacific" on Win9x/Me and "Pacific Standard Time"
211   on WinNT/2k/XP.  There are some other wrinkles; see the code for
212   details.  The subkey name is NOT LOCALIZED, allowing us to support
213   localized installs.
214 
215   Under the subkey are data values.  We care about:
216 
217     Std   Standard time display name, localized
218     TZI   Binary block of data
219 
220   The TZI data is of particular interest.  It contains the offset, two
221   more offsets for standard and daylight time, and the start and end
222   rules.  This is the same data returned by the GetTimeZoneInformation
223   API.  The API may modify the data on the way out, so we have to be
224   careful, but essentially we do a binary comparison against the TZI
225   blocks of various registry keys.  When we find a match, we know what
226   time zone Windows is set to.  Since the registry key is not
227   localized, we can then translate the key through a simple table
228   lookup into the corresponding ICU time zone.
229 
230   This strategy doesn't always work because there are zones which
231   share an offset and rules, so more than one TZI block will match.
232   For example, both Tokyo and Seoul are at GMT+9 with no DST rules;
233   their TZI blocks are identical.  For these cases, we fall back to a
234   name lookup.  We attempt to match the display name as stored in the
235   registry for the current zone to the display name stored in the
236   registry for various Windows zones.  By comparing the registry data
237   directly we avoid conversion complications.
238 
239   Author: Alan Liu
240   Since: ICU 2.6
241   Based on original code by Carl Brown <cbrown@xnetinc.com>
242 */
243 
244 /**
245  * Main Windows time zone detection function.  Returns the Windows
246  * time zone, translated to an ICU time zone, or NULL upon failure.
247  */
248 U_CFUNC const char* U_EXPORT2
uprv_detectWindowsTimeZone()249 uprv_detectWindowsTimeZone() {
250     UErrorCode status = U_ZERO_ERROR;
251     UResourceBundle* bundle = NULL;
252     char* icuid = NULL;
253     UChar apiStd[MAX_LENGTH_ID];
254     char apiStdName[MAX_LENGTH_ID];
255     char regStdName[MAX_LENGTH_ID];
256     char tmpid[MAX_LENGTH_ID];
257     int32_t apiStdLength = 0;
258     int32_t len;
259     int id;
260     int errorCode;
261     char ISOcode[3]; /* 2 letter iso code */
262 
263     LONG result;
264     TZI tziKey;
265     TZI tziReg;
266     TIME_ZONE_INFORMATION apiTZI;
267 
268     /* Obtain TIME_ZONE_INFORMATION from the API, and then convert it
269        to TZI.  We could also interrogate the registry directly; we do
270        this below if needed. */
271     uprv_memset(&apiTZI, 0, sizeof(apiTZI));
272     uprv_memset(&tziKey, 0, sizeof(tziKey));
273     uprv_memset(&tziReg, 0, sizeof(tziReg));
274     GetTimeZoneInformation(&apiTZI);
275     tziKey.bias = apiTZI.Bias;
276     uprv_memcpy((char *)&tziKey.standardDate, (char*)&apiTZI.StandardDate,
277            sizeof(apiTZI.StandardDate));
278     uprv_memcpy((char *)&tziKey.daylightDate, (char*)&apiTZI.DaylightDate,
279            sizeof(apiTZI.DaylightDate));
280 
281     /* Convert the wchar_t* standard name to char* */
282     uprv_memset(apiStdName, 0, sizeof(apiStdName));
283     u_strFromWCS(apiStd, MAX_LENGTH_ID, &apiStdLength, apiTZI.StandardName, -1, &status);
284     u_austrncpy(apiStdName, apiStd, apiStdLength);
285 
286     tmpid[0] = 0;
287 
288     id = GetUserGeoID(GEOCLASS_NATION);
289     errorCode = GetGeoInfo(id,GEO_ISO2,ISOcode,3,0);
290 
291     bundle = ures_openDirect(NULL, "windowsZones", &status);
292     ures_getByKey(bundle, "mapTimezones", bundle, &status);
293 
294     /* Note: We get the winid not from static tables but from resource bundle. */
295     while (U_SUCCESS(status) && ures_hasNext(bundle)) {
296         UBool idFound = FALSE;
297         const char* winid;
298         UResourceBundle* winTZ = ures_getNextResource(bundle, NULL, &status);
299         if (U_FAILURE(status)) {
300             break;
301         }
302         winid = ures_getKey(winTZ);
303         result = getTZI(winid, &tziReg);
304 
305         if (result == ERROR_SUCCESS) {
306             /* Windows alters the DaylightBias in some situations.
307                Using the bias and the rules suffices, so overwrite
308                these unreliable fields. */
309             tziKey.standardBias = tziReg.standardBias;
310             tziKey.daylightBias = tziReg.daylightBias;
311 
312             if (uprv_memcmp((char *)&tziKey, (char*)&tziReg, sizeof(tziKey)) == 0) {
313                 const UChar* icuTZ = NULL;
314                 if (errorCode != 0) {
315                     icuTZ = ures_getStringByKey(winTZ, ISOcode, &len, &status);
316                 }
317                 if (errorCode==0 || icuTZ==NULL) {
318                     /* fallback to default "001" and reset status */
319                     status = U_ZERO_ERROR;
320                     icuTZ = ures_getStringByKey(winTZ, "001", &len, &status);
321                 }
322 
323                 if (U_SUCCESS(status)) {
324                     /* Get the standard name from the registry key to compare with
325                        the one from Windows API call. */
326                     uprv_memset(regStdName, 0, sizeof(regStdName));
327                     result = getSTDName(winid, regStdName, sizeof(regStdName));
328                     if (result == ERROR_SUCCESS) {
329                         if (uprv_strcmp(apiStdName, regStdName) == 0) {
330                             idFound = TRUE;
331                         }
332                     }
333 
334                     /* tmpid buffer holds the ICU timezone ID corresponding to the timezone ID from Windows.
335                      * If none is found, tmpid buffer will contain a fallback ID (i.e. the time zone ID matching
336                      * the current time zone information)
337                      */
338                     if (idFound || tmpid[0] == 0) {
339                         /* if icuTZ has more than one city, take only the first (i.e. terminate icuTZ at first space) */
340                         int index=0;
341                         while (! (*icuTZ == '\0' || *icuTZ ==' ')) {
342                             tmpid[index++]=(char)(*icuTZ++);  /* safe to assume 'char' is ASCII compatible on windows */
343                         }
344                         tmpid[index]='\0';
345                     }
346                 }
347             }
348         }
349         ures_close(winTZ);
350         if (idFound) {
351             break;
352         }
353     }
354 
355     /*
356      * Copy the timezone ID to icuid to be returned.
357      */
358     if (tmpid[0] != 0) {
359         len = uprv_strlen(tmpid);
360         icuid = (char*)uprv_calloc(len + 1, sizeof(char));
361         if (icuid != NULL) {
362             uprv_strcpy(icuid, tmpid);
363         }
364     }
365 
366     ures_close(bundle);
367 
368     return icuid;
369 }
370 
371 #endif /* U_PLATFORM_HAS_WIN32_API */
372