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