3131#endif
3232
3333#if defined(_WIN32)
34- #include < sdkddkver.h>
35- // Include only when the SDK is for Windows 10 (and later), and the binary is
36- // targeted for Windows XP and later.
37- // Note: The Windows SDK added windows.globalization.h file for Windows 10, but
38- // MinGW did not add it until NTDDI_WIN10_NI (SDK version 10.0.22621.0).
39- #if ((defined(_WIN32_WINNT_WIN10) && !defined(__MINGW32__)) || \
40- (defined (NTDDI_WIN10_NI) && NTDDI_VERSION >= NTDDI_WIN10_NI)) && \
41- (_WIN32_WINNT >= _WIN32_WINNT_WINXP)
34+ // Include only when <icu.h> is available.
35+ // https://learn.microsoft.com/en-us/windows/win32/intl/international-components-for-unicode--icu-
36+ // https://devblogs.microsoft.com/oldnewthing/20210527-00/?p=105255
37+ #if defined(__has_include)
38+ #if __has_include(<icu.h>)
4239#define USE_WIN32_LOCAL_TIME_ZONE
43- #include < roapi.h>
44- #include < tchar.h>
45- #include < wchar.h>
46- #include < windows.globalization.h>
40+ #pragma push_macro("NTDDI_VERSION")
41+ // ucal_getTimeZoneIDForWindowsID is available on Windows 10 RS3 and later.
42+ #define NTDDI_VERSION 0x0A000004 // == NTDDI_WIN10_RS3
4743#include < windows.h>
48- #include < winstring.h>
49- #endif
50- #endif
44+ #include < icu.h>
45+ #include < timezoneapi.h>
46+ #pragma pop_macro("NTDDI_VERSION")
47+ #include < atomic>
48+ #endif // __has_include(<icu.h>)
49+ #endif // __has_include
50+ #endif // _WIN32
5151
5252#include < cstdlib>
5353#include < cstring>
@@ -60,80 +60,78 @@ namespace cctz {
6060
6161namespace {
6262#if defined(USE_WIN32_LOCAL_TIME_ZONE)
63- // Calls the WinRT Calendar.GetTimeZone method to obtain the IANA ID of the
64- // local time zone. Returns an empty vector in case of an error.
65- std::string win32_local_time_zone (const HMODULE combase) {
66- std::string result;
67- const auto ro_activate_instance =
68- reinterpret_cast <decltype (&RoActivateInstance)>(
69- GetProcAddress (combase, " RoActivateInstance" ));
70- if (!ro_activate_instance) {
71- return result;
72- }
73- const auto windows_create_string_reference =
74- reinterpret_cast <decltype (&WindowsCreateStringReference)>(
75- GetProcAddress (combase, " WindowsCreateStringReference" ));
76- if (!windows_create_string_reference) {
77- return result;
78- }
79- const auto windows_delete_string =
80- reinterpret_cast <decltype (&WindowsDeleteString)>(
81- GetProcAddress (combase, " WindowsDeleteString" ));
82- if (!windows_delete_string) {
83- return result;
84- }
85- const auto windows_get_string_raw_buffer =
86- reinterpret_cast <decltype (&WindowsGetStringRawBuffer)>(
87- GetProcAddress (combase, " WindowsGetStringRawBuffer" ));
88- if (!windows_get_string_raw_buffer) {
89- return result;
63+ // True if we have already failed to load the API.
64+ static std::atomic_bool g_ucal_getTimeZoneIDForWindowsIDUnavailable;
65+ static std::atomic<decltype (ucal_getTimeZoneIDForWindowsID)*>
66+ g_ucal_getTimeZoneIDForWindowsIDRef;
67+
68+ std::string win32_local_time_zone () {
69+ // If we have already failed to load the API, then just give up.
70+ if (g_ucal_getTimeZoneIDForWindowsIDUnavailable.load ()) {
71+ return " " ;
9072 }
9173
92- // The string returned by WindowsCreateStringReference doesn't need to be
93- // deleted.
94- HSTRING calendar_class_id;
95- HSTRING_HEADER calendar_class_id_header;
96- HRESULT hr = windows_create_string_reference (
97- RuntimeClass_Windows_Globalization_Calendar,
98- sizeof (RuntimeClass_Windows_Globalization_Calendar) / sizeof (wchar_t ) - 1 ,
99- &calendar_class_id_header, &calendar_class_id);
100- if (FAILED (hr)) {
101- return result;
102- }
74+ auto ucal_getTimeZoneIDForWindowsIDFunc =
75+ g_ucal_getTimeZoneIDForWindowsIDRef.load ();
76+ if (ucal_getTimeZoneIDForWindowsIDFunc == nullptr ) {
77+ // If we have already failed to load the API, then just give up.
78+ if (g_ucal_getTimeZoneIDForWindowsIDUnavailable.load ()) {
79+ return " " ;
80+ }
10381
104- IInspectable* calendar;
105- hr = ro_activate_instance (calendar_class_id, &calendar);
106- if (FAILED (hr)) {
107- return result;
82+ const HMODULE icudll = ::LoadLibraryExW (L" icu.dll" , nullptr ,
83+ LOAD_LIBRARY_SEARCH_SYSTEM32);
84+
85+ if (icudll == nullptr ) {
86+ g_ucal_getTimeZoneIDForWindowsIDUnavailable.store (true );
87+ return " " ;
88+ }
89+
90+ ucal_getTimeZoneIDForWindowsIDFunc =
91+ reinterpret_cast <decltype (ucal_getTimeZoneIDForWindowsID)*>(
92+ ::GetProcAddress (icudll, " ucal_getTimeZoneIDForWindowsID" ));
93+
94+ if (ucal_getTimeZoneIDForWindowsIDFunc == nullptr ) {
95+ g_ucal_getTimeZoneIDForWindowsIDUnavailable.store (true );
96+ return " " ;
97+ }
98+ // store-race is not a problem here, because ::GetProcAddress() returns the
99+ // same address for the same function in the same DLL.
100+ g_ucal_getTimeZoneIDForWindowsIDRef.store (
101+ ucal_getTimeZoneIDForWindowsIDFunc);
102+
103+ // We intentionally do not call ::FreeLibrary() here to avoid frequent DLL
104+ // loadings and unloading. As "icu.dll" is a system library, keeping it on
105+ // memory is supposed to have no major drawback.
108106 }
109107
110- ABI::Windows::Globalization::ITimeZoneOnCalendar* time_zone;
111- hr = calendar->QueryInterface (IID_PPV_ARGS (&time_zone));
112- if (FAILED (hr)) {
113- calendar->Release ();
114- return result;
108+ DYNAMIC_TIME_ZONE_INFORMATION info = {};
109+ if (::GetDynamicTimeZoneInformation (&info) == TIME_ZONE_ID_INVALID) {
110+ return " " ;
115111 }
116112
117- HSTRING tz_hstr;
118- hr = time_zone->GetTimeZone (&tz_hstr);
119- if (SUCCEEDED (hr)) {
120- UINT32 wlen;
121- const PCWSTR tz_wstr = windows_get_string_raw_buffer (tz_hstr, &wlen);
122- if (tz_wstr) {
123- const int size =
124- WideCharToMultiByte (CP_UTF8, 0 , tz_wstr, static_cast <int >(wlen),
125- nullptr , 0 , nullptr , nullptr );
126- result.resize (static_cast <size_t >(size));
127- WideCharToMultiByte (CP_UTF8, 0 , tz_wstr, static_cast <int >(wlen),
128- &result[0 ], size, nullptr , nullptr );
129- }
130- windows_delete_string (tz_hstr);
113+ UChar buffer[128 ];
114+ UErrorCode status = U_ZERO_ERROR;
115+ const auto num_chars_in_buffer = ucal_getTimeZoneIDForWindowsIDFunc (
116+ reinterpret_cast <const UChar*>(info.TimeZoneKeyName ), -1 , nullptr ,
117+ buffer, ARRAYSIZE (buffer), &status);
118+ if (status != U_ZERO_ERROR || num_chars_in_buffer <= 0 ||
119+ num_chars_in_buffer > ARRAYSIZE (buffer)) {
120+ return " " ;
131121 }
132- time_zone->Release ();
133- calendar->Release ();
134- return result;
122+
123+ const int num_bytes_in_utf8 = ::WideCharToMultiByte (
124+ CP_UTF8, 0 , reinterpret_cast <const wchar_t *>(buffer),
125+ static_cast <int >(num_chars_in_buffer), nullptr , 0 , nullptr , nullptr );
126+ std::string local_time_str;
127+ local_time_str.resize (static_cast <size_t >(num_bytes_in_utf8));
128+ ::WideCharToMultiByte (CP_UTF8, 0 , reinterpret_cast <const wchar_t *>(buffer),
129+ static_cast<int>(num_chars_in_buffer),
130+ &local_time_str[0], num_bytes_in_utf8, nullptr,
131+ nullptr);
132+ return local_time_str;
135133}
136- #endif
134+ #endif // USE_WIN32_LOCAL_TIME_ZONE
137135} // namespace
138136
139137std::string time_zone::name () const {
@@ -255,36 +253,9 @@ time_zone local_time_zone() {
255253 }
256254#endif
257255#if defined(USE_WIN32_LOCAL_TIME_ZONE)
258- // Use the WinRT Calendar class to get the local time zone. This feature is
259- // available on Windows 10 and later. The library is dynamically linked to
260- // maintain binary compatibility with Windows XP - Windows 7. On Windows 8,
261- // The combase.dll API functions are available but the RoActivateInstance
262- // call will fail for the Calendar class.
263- std::string winrt_tz;
264- const HMODULE combase =
265- LoadLibraryEx (_T (" combase.dll" ), nullptr , LOAD_LIBRARY_SEARCH_SYSTEM32);
266- if (combase) {
267- const auto ro_initialize = reinterpret_cast <decltype (&::RoInitialize)>(
268- GetProcAddress (combase, " RoInitialize" ));
269- const auto ro_uninitialize = reinterpret_cast <decltype (&::RoUninitialize)>(
270- GetProcAddress (combase, " RoUninitialize" ));
271- if (ro_initialize && ro_uninitialize) {
272- const HRESULT hr = ro_initialize (RO_INIT_MULTITHREADED);
273- // RPC_E_CHANGED_MODE means that a previous RoInitialize call specified
274- // a different concurrency model. The WinRT runtime is initialized and
275- // should work for our purpose here, but we should *not* call
276- // RoUninitialize because it's a failure.
277- if (SUCCEEDED (hr) || hr == RPC_E_CHANGED_MODE) {
278- winrt_tz = win32_local_time_zone (combase);
279- if (SUCCEEDED (hr)) {
280- ro_uninitialize ();
281- }
282- }
283- }
284- FreeLibrary (combase);
285- }
286- if (!winrt_tz.empty ()) {
287- zone = winrt_tz.c_str ();
256+ std::string win32_tz = win32_local_time_zone ();
257+ if (!win32_tz.empty ()) {
258+ zone = win32_tz.c_str ();
288259 }
289260#endif
290261
0 commit comments