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 */
34typedef 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 */
46static const char CURRENT_ZONE_REGKEY[] = "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation\\";
47static const char STANDARD_NAME_REGKEY[] = "StandardName";
48static const char STANDARD_TIME_REGKEY[] = " Standard Time";
49static const char TZI_REGKEY[] = "TZI";
50static 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 */
58static 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 */
72static 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 */
85enum {
86    WIN_9X_ME_TYPE = 1,
87    WIN_NT_TYPE = 2,
88    WIN_2K_XP_TYPE = 3
89};
90
91static int32_t gWinType = 0;
92
93static 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
120static 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
151static 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
174static 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 */
248U_CFUNC const char* U_EXPORT2
249uprv_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