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