1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
4 ********************************************************************************
5 * Copyright (C) 2005-2015, International Business Machines
6 * Corporation and others. All Rights Reserved.
7 ********************************************************************************
11 ********************************************************************************
14 #include "unicode/utypes.h"
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)
24 #include "unicode/ures.h"
25 #include "unicode/ustring.h"
27 #ifndef WIN32_LEAN_AND_MEAN
28 # define WIN32_LEAN_AND_MEAN
37 #define MAX_LENGTH_ID 40
39 /* The layout of the Tzi value in the registry */
45 SYSTEMTIME standardDate
;
46 SYSTEMTIME daylightDate
;
50 * Various registry keys and key fragments.
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";
58 * The time zone root keys (under HKLM) for Win7+
60 static const char TZ_REGKEY
[] = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\";
62 static LONG
openTZRegKey(HKEY
*hkey
, const char *winid
)
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.
66 * At its max point this was 111, but the code had 110. Make it 128 for some wiggle room. */
71 uprv_strcpy(subKeyName
, TZ_REGKEY
);
72 name
= &subKeyName
[strlen(subKeyName
)];
73 uprv_strcat(subKeyName
, winid
);
75 result
= RegOpenKeyExA(HKEY_LOCAL_MACHINE
,
83 static LONG
getTZI(const char *winid
, TZI
*tzi
)
85 DWORD cbData
= sizeof(TZI
);
89 result
= openTZRegKey(&hkey
, winid
);
91 if (result
== ERROR_SUCCESS
)
93 result
= RegQueryValueExA(hkey
,
105 static LONG
getSTDName(const char *winid
, char *regStdName
, int32_t length
)
107 DWORD cbData
= length
;
111 result
= openTZRegKey(&hkey
, winid
);
113 if (result
== ERROR_SUCCESS
)
115 result
= RegQueryValueExA(hkey
,
127 static LONG
getTZKeyName(char* tzKeyName
, int32_t tzKeyNamelength
)
131 WCHAR timeZoneKeyNameData
[128];
132 DWORD timeZoneKeyNameLength
= static_cast<DWORD
>(sizeof(timeZoneKeyNameData
));
134 if(ERROR_SUCCESS
== RegOpenKeyExW(
141 if (ERROR_SUCCESS
== RegQueryValueExW(
146 (LPBYTE
)timeZoneKeyNameData
,
147 &timeZoneKeyNameLength
))
149 // Ensure null termination.
150 timeZoneKeyNameData
[UPRV_LENGTHOF(timeZoneKeyNameData
) - 1] = L
'\0';
152 // Convert the UTF-16 string to UTF-8.
153 UErrorCode status
= U_ZERO_ERROR
;
154 u_strToUTF8(tzKeyName
, tzKeyNamelength
, NULL
, reinterpret_cast<const UChar
*>(timeZoneKeyNameData
), -1, &status
);
155 if (U_ZERO_ERROR
== status
)
157 result
= ERROR_SUCCESS
;
167 This code attempts to detect the Windows time zone directly,
168 as set in the Windows Date and Time control panel. It attempts
169 to work on versions greater than Windows Vista and on localized
170 installs. It works by directly interrogating the registry and
171 comparing the data there with the data returned by the
172 GetTimeZoneInformation API, along with some other strategies. The
173 registry contains time zone data under this key:
175 HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\
177 Under this key are several subkeys, one for each time zone. For
178 example these subkeys are named "Pacific Standard Time" on Vista+.
179 There are some other wrinkles; see the code for
180 details. The subkey name is NOT LOCALIZED, allowing us to support
183 Under the subkey are data values. We care about:
185 Std Standard time display name, localized
186 TZI Binary block of data
188 The TZI data is of particular interest. It contains the offset, two
189 more offsets for standard and daylight time, and the start and end
190 rules. This is the same data returned by the GetTimeZoneInformation
191 API. The API may modify the data on the way out, so we have to be
192 careful, but essentially we do a binary comparison against the TZI
193 blocks of various registry keys. When we find a match, we know what
194 time zone Windows is set to. Since the registry key is not
195 localized, we can then translate the key through a simple table
196 lookup into the corresponding ICU time zone.
198 This strategy doesn't always work because there are zones which
199 share an offset and rules, so more than one TZI block will match.
200 For example, both Tokyo and Seoul are at GMT+9 with no DST rules;
201 their TZI blocks are identical. For these cases, we fall back to a
202 name lookup. We attempt to match the display name as stored in the
203 registry for the current zone to the display name stored in the
204 registry for various Windows zones. By comparing the registry data
205 directly we avoid conversion complications.
209 Based on original code by Carl Brown <cbrown@xnetinc.com>
213 * Main Windows time zone detection function. Returns the Windows
214 * time zone, translated to an ICU time zone, or NULL upon failure.
216 U_CFUNC
const char* U_EXPORT2
217 uprv_detectWindowsTimeZone()
219 UErrorCode status
= U_ZERO_ERROR
;
220 UResourceBundle
* bundle
= NULL
;
222 char apiStdName
[MAX_LENGTH_ID
];
223 char regStdName
[MAX_LENGTH_ID
];
224 char tmpid
[MAX_LENGTH_ID
];
228 wchar_t ISOcodeW
[3]; /* 2 letter iso code in UTF-16*/
229 char ISOcodeA
[3]; /* 2 letter iso code in ansi */
234 TIME_ZONE_INFORMATION apiTZI
;
236 BOOL tryPreVistaFallback
;
237 OSVERSIONINFO osVerInfo
;
239 /* Obtain TIME_ZONE_INFORMATION from the API, and then convert it
240 to TZI. We could also interrogate the registry directly; we do
241 this below if needed. */
242 uprv_memset(&apiTZI
, 0, sizeof(apiTZI
));
243 uprv_memset(&tziKey
, 0, sizeof(tziKey
));
244 uprv_memset(&tziReg
, 0, sizeof(tziReg
));
245 GetTimeZoneInformation(&apiTZI
);
246 tziKey
.bias
= apiTZI
.Bias
;
247 uprv_memcpy((char *)&tziKey
.standardDate
, (char*)&apiTZI
.StandardDate
,
248 sizeof(apiTZI
.StandardDate
));
249 uprv_memcpy((char *)&tziKey
.daylightDate
, (char*)&apiTZI
.DaylightDate
,
250 sizeof(apiTZI
.DaylightDate
));
252 /* Convert the wchar_t* standard name to char* */
253 uprv_memset(apiStdName
, 0, sizeof(apiStdName
));
254 wcstombs(apiStdName
, apiTZI
.StandardName
, MAX_LENGTH_ID
);
258 id
= GetUserGeoID(GEOCLASS_NATION
);
259 errorCode
= GetGeoInfoW(id
, GEO_ISO2
, ISOcodeW
, 3, 0);
260 u_strToUTF8(ISOcodeA
, 3, NULL
, (const UChar
*)ISOcodeW
, 3, &status
);
262 bundle
= ures_openDirect(NULL
, "windowsZones", &status
);
263 ures_getByKey(bundle
, "mapTimezones", bundle
, &status
);
266 Windows Vista+ provides us with a "TimeZoneKeyName" that is not localized
267 and can be used to directly map a name in our bundle. Try to use that first
268 if we're on Vista or higher
270 uprv_memset(&osVerInfo
, 0, sizeof(osVerInfo
));
271 osVerInfo
.dwOSVersionInfoSize
= sizeof(osVerInfo
);
272 tryPreVistaFallback
= TRUE
;
273 result
= getTZKeyName(regStdName
, sizeof(regStdName
));
274 if(ERROR_SUCCESS
== result
)
276 UResourceBundle
* winTZ
= ures_getByKey(bundle
, regStdName
, NULL
, &status
);
277 if(U_SUCCESS(status
))
279 const UChar
* icuTZ
= NULL
;
282 icuTZ
= ures_getStringByKey(winTZ
, ISOcodeA
, &len
, &status
);
284 if (errorCode
==0 || icuTZ
==NULL
)
286 /* fallback to default "001" and reset status */
287 status
= U_ZERO_ERROR
;
288 icuTZ
= ures_getStringByKey(winTZ
, "001", &len
, &status
);
291 if(U_SUCCESS(status
))
294 while (! (*icuTZ
== '\0' || *icuTZ
==' '))
296 tmpid
[index
++]=(char)(*icuTZ
++); /* safe to assume 'char' is ASCII compatible on windows */
299 tryPreVistaFallback
= FALSE
;
305 if(tryPreVistaFallback
)
307 /* Note: We get the winid not from static tables but from resource bundle. */
308 while (U_SUCCESS(status
) && ures_hasNext(bundle
))
310 UBool idFound
= FALSE
;
312 UResourceBundle
* winTZ
= ures_getNextResource(bundle
, NULL
, &status
);
313 if (U_FAILURE(status
))
317 winid
= ures_getKey(winTZ
);
318 result
= getTZI(winid
, &tziReg
);
320 if (result
== ERROR_SUCCESS
)
322 /* Windows alters the DaylightBias in some situations.
323 Using the bias and the rules suffices, so overwrite
324 these unreliable fields. */
325 tziKey
.standardBias
= tziReg
.standardBias
;
326 tziKey
.daylightBias
= tziReg
.daylightBias
;
328 if (uprv_memcmp((char *)&tziKey
, (char*)&tziReg
, sizeof(tziKey
)) == 0)
330 const UChar
* icuTZ
= NULL
;
333 icuTZ
= ures_getStringByKey(winTZ
, ISOcodeA
, &len
, &status
);
335 if (errorCode
==0 || icuTZ
==NULL
)
337 /* fallback to default "001" and reset status */
338 status
= U_ZERO_ERROR
;
339 icuTZ
= ures_getStringByKey(winTZ
, "001", &len
, &status
);
342 if (U_SUCCESS(status
))
344 /* Get the standard name from the registry key to compare with
345 the one from Windows API call. */
346 uprv_memset(regStdName
, 0, sizeof(regStdName
));
347 result
= getSTDName(winid
, regStdName
, sizeof(regStdName
));
348 if (result
== ERROR_SUCCESS
)
350 if (uprv_strcmp(apiStdName
, regStdName
) == 0)
356 /* tmpid buffer holds the ICU timezone ID corresponding to the timezone ID from Windows.
357 * If none is found, tmpid buffer will contain a fallback ID (i.e. the time zone ID matching
358 * the current time zone information)
360 if (idFound
|| tmpid
[0] == 0)
362 /* if icuTZ has more than one city, take only the first (i.e. terminate icuTZ at first space) */
364 while (! (*icuTZ
== '\0' || *icuTZ
==' '))
366 tmpid
[index
++]=(char)(*icuTZ
++); /* safe to assume 'char' is ASCII compatible on windows */
382 * Copy the timezone ID to icuid to be returned.
386 len
= uprv_strlen(tmpid
);
387 icuid
= (char*)uprv_calloc(len
+ 1, sizeof(char));
390 uprv_strcpy(icuid
, tmpid
);
399 #endif /* U_PLATFORM_USES_ONLY_WIN32_API && (U_PLATFORM_HAS_WINUWP_API == 0) */