1/*
2********************************************************************************
3*   Copyright (C) 2005-2010, 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#ifdef U_WINDOWS
15
16#include "wintz.h"
17
18#include "cmemory.h"
19#include "cstring.h"
20
21#include "unicode/ustring.h"
22#include "unicode/ures.h"
23
24#   define WIN32_LEAN_AND_MEAN
25#   define VC_EXTRALEAN
26#   define NOUSER
27#   define NOSERVICE
28#   define NOIME
29#   define NOMCX
30#include <windows.h>
31
32/* The layout of the Tzi value in the registry */
33typedef struct
34{
35    int32_t bias;
36    int32_t standardBias;
37    int32_t daylightBias;
38    SYSTEMTIME standardDate;
39    SYSTEMTIME daylightDate;
40} TZI;
41
42/**
43 * Various registry keys and key fragments.
44 */
45static const char CURRENT_ZONE_REGKEY[] = "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation\\";
46static const char STANDARD_NAME_REGKEY[] = "StandardName";
47static const char STANDARD_TIME_REGKEY[] = " Standard Time";
48static const char TZI_REGKEY[] = "TZI";
49static const char STD_REGKEY[] = "Std";
50
51/**
52 * HKLM subkeys used to probe for the flavor of Windows.  Note that we
53 * specifically check for the "GMT" zone subkey; this is present on
54 * NT, but on XP has become "GMT Standard Time".  We need to
55 * discriminate between these cases.
56 */
57static const char* const WIN_TYPE_PROBE_REGKEY[] = {
58    /* WIN_9X_ME_TYPE */
59    "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones",
60
61    /* WIN_NT_TYPE */
62    "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\GMT"
63
64    /* otherwise: WIN_2K_XP_TYPE */
65};
66
67/**
68 * The time zone root subkeys (under HKLM) for different flavors of
69 * Windows.
70 */
71static const char* const TZ_REGKEY[] = {
72    /* WIN_9X_ME_TYPE */
73    "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones\\",
74
75    /* WIN_NT_TYPE | WIN_2K_XP_TYPE */
76    "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\"
77};
78
79/**
80 * Flavor of Windows, from our perspective.  Not a real OS version,
81 * but rather the flavor of the layout of the time zone information in
82 * the registry.
83 */
84enum {
85    WIN_9X_ME_TYPE = 1,
86    WIN_NT_TYPE = 2,
87    WIN_2K_XP_TYPE = 3
88};
89
90static int32_t gWinType = 0;
91
92static int32_t detectWindowsType()
93{
94    int32_t winType;
95    LONG result;
96    HKEY hkey;
97
98    /* Detect the version of windows by trying to open a sequence of
99        probe keys.  We don't use the OS version API because what we
100        really want to know is how the registry is laid out.
101        Specifically, is it 9x/Me or not, and is it "GMT" or "GMT
102        Standard Time". */
103    for (winType = 0; winType < 2; winType++) {
104        result = RegOpenKeyExA(HKEY_LOCAL_MACHINE,
105                              WIN_TYPE_PROBE_REGKEY[winType],
106                              0,
107                              KEY_QUERY_VALUE,
108                              &hkey);
109        RegCloseKey(hkey);
110
111        if (result == ERROR_SUCCESS) {
112            break;
113        }
114    }
115
116    return winType+1; // +1 to bring it inline with the enum
117}
118
119static LONG openTZRegKey(HKEY *hkey, const char *winid)
120{
121    char subKeyName[110]; /* TODO: why 96?? */
122    char *name;
123    LONG result;
124
125    /* This isn't thread safe, but it's good enough because the result should be constant per system. */
126    if (gWinType <= 0) {
127        gWinType = detectWindowsType();
128    }
129
130    uprv_strcpy(subKeyName, TZ_REGKEY[(gWinType != WIN_9X_ME_TYPE)]);
131    name = &subKeyName[strlen(subKeyName)];
132    uprv_strcat(subKeyName, winid);
133
134    if (gWinType == WIN_9X_ME_TYPE) {
135        /* Remove " Standard Time" */
136        char *pStd = uprv_strstr(subKeyName, STANDARD_TIME_REGKEY);
137        if (pStd) {
138            *pStd = 0;
139        }
140    }
141
142    result = RegOpenKeyExA(HKEY_LOCAL_MACHINE,
143                            subKeyName,
144                            0,
145                            KEY_QUERY_VALUE,
146                            hkey);
147    return result;
148}
149
150static LONG getTZI(const char *winid, TZI *tzi)
151{
152    DWORD cbData = sizeof(TZI);
153    LONG result;
154    HKEY hkey;
155
156    result = openTZRegKey(&hkey, winid);
157
158    if (result == ERROR_SUCCESS) {
159        result = RegQueryValueExA(hkey,
160                                    TZI_REGKEY,
161                                    NULL,
162                                    NULL,
163                                    (LPBYTE)tzi,
164                                    &cbData);
165
166    }
167
168    RegCloseKey(hkey);
169
170    return result;
171}
172
173/*
174  This code attempts to detect the Windows time zone, as set in the
175  Windows Date and Time control panel.  It attempts to work on
176  multiple flavors of Windows (9x, Me, NT, 2000, XP) and on localized
177  installs.  It works by directly interrogating the registry and
178  comparing the data there with the data returned by the
179  GetTimeZoneInformation API, along with some other strategies.  The
180  registry contains time zone data under one of two keys (depending on
181  the flavor of Windows):
182
183    HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones\
184    HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\
185
186  Under this key are several subkeys, one for each time zone.  These
187  subkeys are named "Pacific" on Win9x/Me and "Pacific Standard Time"
188  on WinNT/2k/XP.  There are some other wrinkles; see the code for
189  details.  The subkey name is NOT LOCALIZED, allowing us to support
190  localized installs.
191
192  Under the subkey are data values.  We care about:
193
194    Std   Standard time display name, localized
195    TZI   Binary block of data
196
197  The TZI data is of particular interest.  It contains the offset, two
198  more offsets for standard and daylight time, and the start and end
199  rules.  This is the same data returned by the GetTimeZoneInformation
200  API.  The API may modify the data on the way out, so we have to be
201  careful, but essentially we do a binary comparison against the TZI
202  blocks of various registry keys.  When we find a match, we know what
203  time zone Windows is set to.  Since the registry key is not
204  localized, we can then translate the key through a simple table
205  lookup into the corresponding ICU time zone.
206
207  This strategy doesn't always work because there are zones which
208  share an offset and rules, so more than one TZI block will match.
209  For example, both Tokyo and Seoul are at GMT+9 with no DST rules;
210  their TZI blocks are identical.  For these cases, we fall back to a
211  name lookup.  We attempt to match the display name as stored in the
212  registry for the current zone to the display name stored in the
213  registry for various Windows zones.  By comparing the registry data
214  directly we avoid conversion complications.
215
216  Author: Alan Liu
217  Since: ICU 2.6
218  Based on original code by Carl Brown <cbrown@xnetinc.com>
219*/
220
221/**
222 * Main Windows time zone detection function.  Returns the Windows
223 * time zone, translated to an ICU time zone, or NULL upon failure.
224 */
225U_CFUNC const char* U_EXPORT2
226uprv_detectWindowsTimeZone() {
227    UErrorCode status = U_ZERO_ERROR;
228    UResourceBundle* bundle = NULL;
229    char* icuid = NULL;
230
231    LONG result;
232    TZI tziKey;
233    TZI tziReg;
234    TIME_ZONE_INFORMATION apiTZI;
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    bundle = ures_openDirect(NULL, "windowsZones", &status);
250    ures_getByKey(bundle, "mapTimezones", bundle, &status);
251
252    /* Note: We get the winid not from static tables but from resource bundle. */
253    while (U_SUCCESS(status) && ures_hasNext(bundle)) {
254        const char* winid;
255        int32_t len;
256        UResourceBundle* winTZ = ures_getNextResource(bundle, NULL, &status);
257        if (U_FAILURE(status)) {
258            break;
259        }
260        winid = ures_getKey(winTZ);
261        result = getTZI(winid, &tziReg);
262
263        if (result == ERROR_SUCCESS) {
264            /* Windows alters the DaylightBias in some situations.
265               Using the bias and the rules suffices, so overwrite
266               these unreliable fields. */
267            tziKey.standardBias = tziReg.standardBias;
268            tziKey.daylightBias = tziReg.daylightBias;
269
270            if (uprv_memcmp((char *)&tziKey, (char*)&tziReg, sizeof(tziKey)) == 0) {
271                const UChar* icuTZ = ures_getStringByKey(winTZ, "001", &len, &status);
272                if (U_SUCCESS(status)) {
273                    icuid = (char*)uprv_malloc(sizeof(char) * (len + 1));
274                    uprv_memset(icuid, 0, len + 1);
275                    u_austrncpy(icuid, icuTZ, len);
276                }
277            }
278        }
279        ures_close(winTZ);
280        if (icuid != NULL) {
281            break;
282        }
283    }
284
285    ures_close(bundle);
286
287    return icuid;
288}
289
290#endif /* #ifdef U_WINDOWS */
291