• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2 ********************************************************************************
3 *   Copyright (C) 2005-2013, 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 len;
258     int id;
259     int errorCode;
260     char ISOcode[3]; /* 2 letter iso code */
261 
262     LONG result;
263     TZI tziKey;
264     TZI tziReg;
265     TIME_ZONE_INFORMATION apiTZI;
266 
267     /* Obtain TIME_ZONE_INFORMATION from the API, and then convert it
268        to TZI.  We could also interrogate the registry directly; we do
269        this below if needed. */
270     uprv_memset(&apiTZI, 0, sizeof(apiTZI));
271     uprv_memset(&tziKey, 0, sizeof(tziKey));
272     uprv_memset(&tziReg, 0, sizeof(tziReg));
273     GetTimeZoneInformation(&apiTZI);
274     tziKey.bias = apiTZI.Bias;
275     uprv_memcpy((char *)&tziKey.standardDate, (char*)&apiTZI.StandardDate,
276            sizeof(apiTZI.StandardDate));
277     uprv_memcpy((char *)&tziKey.daylightDate, (char*)&apiTZI.DaylightDate,
278            sizeof(apiTZI.DaylightDate));
279 
280     /* Convert the wchar_t* standard name to char* */
281     uprv_memset(apiStdName, 0, sizeof(apiStdName));
282     u_strFromWCS(apiStd, MAX_LENGTH_ID, NULL, apiTZI.StandardName, -1, &status);
283     u_austrncpy(apiStdName, apiStd, sizeof(apiStdName) - 1);
284 
285     tmpid[0] = 0;
286 
287     id = GetUserGeoID(GEOCLASS_NATION);
288     errorCode = GetGeoInfoA(id,GEO_ISO2,ISOcode,3,0);
289 
290     bundle = ures_openDirect(NULL, "windowsZones", &status);
291     ures_getByKey(bundle, "mapTimezones", bundle, &status);
292 
293     /* Note: We get the winid not from static tables but from resource bundle. */
294     while (U_SUCCESS(status) && ures_hasNext(bundle)) {
295         UBool idFound = FALSE;
296         const char* winid;
297         UResourceBundle* winTZ = ures_getNextResource(bundle, NULL, &status);
298         if (U_FAILURE(status)) {
299             break;
300         }
301         winid = ures_getKey(winTZ);
302         result = getTZI(winid, &tziReg);
303 
304         if (result == ERROR_SUCCESS) {
305             /* Windows alters the DaylightBias in some situations.
306                Using the bias and the rules suffices, so overwrite
307                these unreliable fields. */
308             tziKey.standardBias = tziReg.standardBias;
309             tziKey.daylightBias = tziReg.daylightBias;
310 
311             if (uprv_memcmp((char *)&tziKey, (char*)&tziReg, sizeof(tziKey)) == 0) {
312                 const UChar* icuTZ = NULL;
313                 if (errorCode != 0) {
314                     icuTZ = ures_getStringByKey(winTZ, ISOcode, &len, &status);
315                 }
316                 if (errorCode==0 || icuTZ==NULL) {
317                     /* fallback to default "001" and reset status */
318                     status = U_ZERO_ERROR;
319                     icuTZ = ures_getStringByKey(winTZ, "001", &len, &status);
320                 }
321 
322                 if (U_SUCCESS(status)) {
323                     /* Get the standard name from the registry key to compare with
324                        the one from Windows API call. */
325                     uprv_memset(regStdName, 0, sizeof(regStdName));
326                     result = getSTDName(winid, regStdName, sizeof(regStdName));
327                     if (result == ERROR_SUCCESS) {
328                         if (uprv_strcmp(apiStdName, regStdName) == 0) {
329                             idFound = TRUE;
330                         }
331                     }
332 
333                     /* tmpid buffer holds the ICU timezone ID corresponding to the timezone ID from Windows.
334                      * If none is found, tmpid buffer will contain a fallback ID (i.e. the time zone ID matching
335                      * the current time zone information)
336                      */
337                     if (idFound || tmpid[0] == 0) {
338                         /* if icuTZ has more than one city, take only the first (i.e. terminate icuTZ at first space) */
339                         int index=0;
340                         while (! (*icuTZ == '\0' || *icuTZ ==' ')) {
341                             tmpid[index++]=(char)(*icuTZ++);  /* safe to assume 'char' is ASCII compatible on windows */
342                         }
343                         tmpid[index]='\0';
344                     }
345                 }
346             }
347         }
348         ures_close(winTZ);
349         if (idFound) {
350             break;
351         }
352     }
353 
354     /*
355      * Copy the timezone ID to icuid to be returned.
356      */
357     if (tmpid[0] != 0) {
358         len = uprv_strlen(tmpid);
359         icuid = (char*)uprv_calloc(len + 1, sizeof(char));
360         if (icuid != NULL) {
361             uprv_strcpy(icuid, tmpid);
362         }
363     }
364 
365     ures_close(bundle);
366 
367     return icuid;
368 }
369 
370 #endif /* U_PLATFORM_HAS_WIN32_API */
371