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