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