33
33
#endif
34
34
35
35
#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>)
44
41
#define USE_WIN32_LOCAL_TIME_ZONE
45
- #include < roapi.h>
46
- #include < tchar.h>
47
- #include < wchar.h>
48
- #include < windows.globalization.h>
49
42
#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
53
59
54
60
#include < cstdlib>
55
61
#include < cstring>
@@ -65,80 +71,78 @@ namespace cctz {
65
71
66
72
namespace {
67
73
#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 " " ;
95
83
}
96
84
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
+ }
108
92
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.
113
117
}
114
118
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 " " ;
120
122
}
121
123
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 " " ;
136
132
}
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;
140
144
}
141
- #endif
145
+ #endif // USE_WIN32_LOCAL_TIME_ZONE
142
146
} // namespace
143
147
144
148
std::string time_zone::name () const { return effective_impl ().Name (); }
@@ -256,36 +260,9 @@ time_zone local_time_zone() {
256
260
}
257
261
#endif
258
262
#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 ();
289
266
}
290
267
#endif
291
268
0 commit comments