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 char CURRENT_ZONE_REGKEY
[] = "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 length
)
131 DWORD cbData
= length
;
133 if(ERROR_SUCCESS
== RegOpenKeyExA(
140 result
= RegQueryValueExA(
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
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
161 registry contains time zone data under this key:
163 HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\
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
168 details. The subkey name is NOT LOCALIZED, allowing us to support
171 Under the subkey are data values. We care about:
173 Std Standard time display name, localized
174 TZI Binary block of data
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.
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.
197 Based on original code by Carl Brown <cbrown@xnetinc.com>
201 * Main Windows time zone detection function. Returns the Windows
202 * time zone, translated to an ICU time zone, or NULL upon failure.
204 U_CFUNC
const char* U_EXPORT2
205 uprv_detectWindowsTimeZone()
207 UErrorCode status
= U_ZERO_ERROR
;
208 UResourceBundle
* bundle
= NULL
;
210 char apiStdName
[MAX_LENGTH_ID
];
211 char regStdName
[MAX_LENGTH_ID
];
212 char tmpid
[MAX_LENGTH_ID
];
216 wchar_t ISOcodeW
[3]; /* 2 letter iso code in UTF-16*/
217 char ISOcodeA
[3]; /* 2 letter iso code in ansi */
222 TIME_ZONE_INFORMATION apiTZI
;
224 BOOL tryPreVistaFallback
;
225 OSVERSIONINFO osVerInfo
;
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
));
240 /* Convert the wchar_t* standard name to char* */
241 uprv_memset(apiStdName
, 0, sizeof(apiStdName
));
242 wcstombs(apiStdName
, apiTZI
.StandardName
, MAX_LENGTH_ID
);
246 id
= GetUserGeoID(GEOCLASS_NATION
);
247 errorCode
= GetGeoInfoW(id
, GEO_ISO2
, ISOcodeW
, 3, 0);
248 u_strToUTF8(ISOcodeA
, 3, NULL
, (const UChar
*)ISOcodeW
, 3, &status
);
250 bundle
= ures_openDirect(NULL
, "windowsZones", &status
);
251 ures_getByKey(bundle
, "mapTimezones", bundle
, &status
);
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
258 uprv_memset(&osVerInfo
, 0, sizeof(osVerInfo
));
259 osVerInfo
.dwOSVersionInfoSize
= sizeof(osVerInfo
);
260 tryPreVistaFallback
= TRUE
;
261 result
= getTZKeyName(regStdName
, sizeof(regStdName
));
262 if(ERROR_SUCCESS
== result
)
264 UResourceBundle
* winTZ
= ures_getByKey(bundle
, regStdName
, NULL
, &status
);
265 if(U_SUCCESS(status
))
267 const UChar
* icuTZ
= NULL
;
270 icuTZ
= ures_getStringByKey(winTZ
, ISOcodeA
, &len
, &status
);
272 if (errorCode
==0 || icuTZ
==NULL
)
274 /* fallback to default "001" and reset status */
275 status
= U_ZERO_ERROR
;
276 icuTZ
= ures_getStringByKey(winTZ
, "001", &len
, &status
);
279 if(U_SUCCESS(status
))
282 while (! (*icuTZ
== '\0' || *icuTZ
==' '))
284 tmpid
[index
++]=(char)(*icuTZ
++); /* safe to assume 'char' is ASCII compatible on windows */
287 tryPreVistaFallback
= FALSE
;
293 if(tryPreVistaFallback
)
295 /* Note: We get the winid not from static tables but from resource bundle. */
296 while (U_SUCCESS(status
) && ures_hasNext(bundle
))
298 UBool idFound
= FALSE
;
300 UResourceBundle
* winTZ
= ures_getNextResource(bundle
, NULL
, &status
);
301 if (U_FAILURE(status
))
305 winid
= ures_getKey(winTZ
);
306 result
= getTZI(winid
, &tziReg
);
308 if (result
== ERROR_SUCCESS
)
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
;
316 if (uprv_memcmp((char *)&tziKey
, (char*)&tziReg
, sizeof(tziKey
)) == 0)
318 const UChar
* icuTZ
= NULL
;
321 icuTZ
= ures_getStringByKey(winTZ
, ISOcodeA
, &len
, &status
);
323 if (errorCode
==0 || icuTZ
==NULL
)
325 /* fallback to default "001" and reset status */
326 status
= U_ZERO_ERROR
;
327 icuTZ
= ures_getStringByKey(winTZ
, "001", &len
, &status
);
330 if (U_SUCCESS(status
))
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
));
336 if (result
== ERROR_SUCCESS
)
338 if (uprv_strcmp(apiStdName
, regStdName
) == 0)
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)
348 if (idFound
|| tmpid
[0] == 0)
350 /* if icuTZ has more than one city, take only the first (i.e. terminate icuTZ at first space) */
352 while (! (*icuTZ
== '\0' || *icuTZ
==' '))
354 tmpid
[index
++]=(char)(*icuTZ
++); /* safe to assume 'char' is ASCII compatible on windows */
370 * Copy the timezone ID to icuid to be returned.
374 len
= uprv_strlen(tmpid
);
375 icuid
= (char*)uprv_calloc(len
+ 1, sizeof(char));
378 uprv_strcpy(icuid
, tmpid
);
387 #endif /* U_PLATFORM_USES_ONLY_WIN32_API && (U_PLATFORM_HAS_WINUWP_API == 0) */