Skip to content

Commit 4af4c81

Browse files
committed
Reimplement GetWindowsLocalTimeZone without icu.h
This commit reimplements my previous commits [1][2], which removed the dependency on WinRT on Windows by using system "icu.dll" (#315) when <icu.h> is available in the build environment. Here are major improvements: * The relevant code is extracted into separate source files "src/icu_win.{cc,h}". This is also a preparation for implementing TimeZoneIf with Windows time APIs (#328), where one more ICU API needs to be dynamically imported. * The dependency on <icu.h> is removed by locally defining required constants and API prototypes. This should make it possible to build CCTZ with the MinGW toolchain, where <icu.h> is not yet available. * On Windows 11 or later, a newer and more straightforward ICU API "ucal_getHostTimeZone" is used. If it is not available, then the previous approach ("GetDynamicTimeZoneInformation" + "ucal_getTimeZoneIDForWindowsID") continues to be used as a fallback. * LoadLibraryExW(L"icu.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32) is replaced with LoadLibraryW with the full path to "icu.dll" in the System32 directory, because LOAD_LIBRARY_SEARCH_SYSTEM32 can be ignored when "icu.dll" is already loaded from a non-System32 directory. Let's stick to the system "icu.dll" in favor of predictability. Other than the above improvements, the overall logic remains unchanged. [1]: 2ebbe0d [2]: f8e494f
1 parent c6faf07 commit 4af4c81

File tree

5 files changed

+287
-103
lines changed

5 files changed

+287
-103
lines changed

BUILD

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,13 @@ cc_library(
5858
"src/time_zone_posix.h",
5959
"src/tzfile.h",
6060
"src/zone_info_source.cc",
61-
],
61+
] + select({
62+
"@platforms//os:windows": [
63+
"src/icu_win.cc",
64+
"src/icu_win.h",
65+
],
66+
"//conditions:default": [],
67+
}),
6268
hdrs = [
6369
"include/cctz/time_zone.h",
6470
"include/cctz/zone_info_source.h",

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ add_library(cctz
8585
src/time_zone_posix.h
8686
src/tzfile.h
8787
src/zone_info_source.cc
88+
$<IF:$<BOOL:WIN32>,src/icu_win.cc,>
89+
$<IF:$<BOOL:WIN32>,src/icu_win.h,>
8890
${CCTZ_HDRS}
8991
)
9092
cctz_target_set_cxx_standard(cctz)

src/icu_win.cc

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
// Copyright 2025 Google Inc. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "icu_win.h"
16+
17+
#if defined(_WIN32)
18+
19+
#if !defined(NOMINMAX)
20+
#define NOMINMAX
21+
#endif // !defined(NOMINMAX)
22+
#include <windows.h>
23+
24+
#include <atomic>
25+
#include <cstdint>
26+
#include <limits>
27+
#include <memory>
28+
#include <string>
29+
#include <type_traits>
30+
#include <utility>
31+
32+
namespace cctz {
33+
namespace icu {
34+
namespace {
35+
36+
// UChar is defined as char16_t in ICU by default, but it is also safe to assume
37+
// wchar_t and char16_t are equivalent on Windows.
38+
using UChar = wchar_t;
39+
40+
enum UErrorCode : std::int32_t {
41+
U_ZERO_ERROR = 0,
42+
U_BUFFER_OVERFLOW_ERROR = 15,
43+
};
44+
45+
bool U_SUCCESS(UErrorCode error) { return error <= U_ZERO_ERROR; }
46+
47+
// ICU function signatures
48+
using ucal_getHostTimeZone_func = std::int32_t(__cdecl*)(
49+
UChar* result, std::int32_t result_capacity, UErrorCode* status);
50+
using ucal_getTimeZoneIDForWindowsID_func = std::int32_t(__cdecl*)(
51+
const UChar* winid, std::int32_t len, const char* region, UChar* id,
52+
std::int32_t id_capacity, UErrorCode* status);
53+
54+
std::atomic<bool> g_unavailable;
55+
std::atomic<ucal_getHostTimeZone_func> g_ucal_getHostTimeZone;
56+
std::atomic<ucal_getTimeZoneIDForWindowsID_func>
57+
g_ucal_getTimeZoneIDForWindowsID;
58+
59+
struct IcuFunctions {
60+
const bool available;
61+
const ucal_getHostTimeZone_func ucal_getHostTimeZone;
62+
const ucal_getTimeZoneIDForWindowsID_func ucal_getTimeZoneIDForWindowsID;
63+
};
64+
65+
IcuFunctions Unavailable() { return {false, nullptr, nullptr}; }
66+
67+
template <typename T>
68+
static T AsProcAddress(HMODULE module, const char* name) {
69+
static_assert(
70+
std::is_pointer<T>::value &&
71+
std::is_function<typename std::remove_pointer<T>::type>::value,
72+
"T must be a function pointer type");
73+
const auto proc_address = ::GetProcAddress(module, name);
74+
return reinterpret_cast<T>(reinterpret_cast<void*>(proc_address));
75+
}
76+
77+
std::wstring GetSystem32Dir() {
78+
size_t dynamic_buffer_size = 0;
79+
{
80+
// Fast-path. Usually it's "C:\Windows\System32", which is 19 characters.
81+
const size_t buffer_size = 32;
82+
wchar_t buffer[buffer_size];
83+
UINT result = ::GetSystemDirectoryW(buffer, buffer_size);
84+
// If successful, the return value is the length of the string without NUL.
85+
if (result <= buffer_size) {
86+
return std::wstring(buffer, result);
87+
}
88+
// Otherwise, the return value is the required length + 1 for NUL.
89+
dynamic_buffer_size = result;
90+
}
91+
// Slow-path. Allocate a buffer of the required size.
92+
std::unique_ptr<wchar_t[]> buffer(new wchar_t[dynamic_buffer_size]);
93+
const UINT length = ::GetSystemDirectoryW(buffer.get(), dynamic_buffer_size);
94+
if (length <= dynamic_buffer_size) {
95+
return std::wstring(buffer.get(), length);
96+
}
97+
return std::wstring();
98+
}
99+
100+
IcuFunctions GetIcuFunctions() {
101+
if (g_unavailable.load(std::memory_order_relaxed)) {
102+
return Unavailable();
103+
}
104+
105+
// Check if already loaded
106+
{
107+
const auto ucal_getHostTimeZoneRef =
108+
g_ucal_getHostTimeZone.load(std::memory_order_relaxed);
109+
const auto ucal_getTimeZoneIDForWindowsIDRef =
110+
g_ucal_getTimeZoneIDForWindowsID.load(std::memory_order_relaxed);
111+
112+
// Note: ucal_getHostTimeZone can be unavailable on older Windows.
113+
if (ucal_getTimeZoneIDForWindowsIDRef != nullptr) {
114+
return {true, ucal_getHostTimeZoneRef, ucal_getTimeZoneIDForWindowsIDRef};
115+
}
116+
}
117+
118+
// Our goal here is to load the ICU DLL from the system directory, even when
119+
// the current process has loaded "icu.dll" from somewhere other than the
120+
// system directory. To do so we must use the full path here.
121+
const std::wstring icu_dll_path = GetSystem32Dir() + L"\\icu.dll";
122+
123+
// CAVEAT: LoadLibraryExW with LOAD_LIBRARY_SEARCH_SYSTEM32 is not
124+
// sufficient when "icu.dll" is already loaded from somewhere other than
125+
// the system directory. This is why we must pass a full path here.
126+
const HMODULE icu_dll = ::LoadLibraryW(icu_dll_path.c_str());
127+
if (icu_dll == nullptr) {
128+
g_unavailable.store(true, std::memory_order_relaxed);
129+
return Unavailable();
130+
}
131+
132+
const auto ucal_getHostTimeZoneRef =
133+
AsProcAddress<ucal_getHostTimeZone_func>(icu_dll, "ucal_getHostTimeZone");
134+
const auto ucal_getTimeZoneIDForWindowsIDRef =
135+
AsProcAddress<ucal_getTimeZoneIDForWindowsID_func>(
136+
icu_dll, "ucal_getTimeZoneIDForWindowsID");
137+
138+
// Note: ucal_getHostTimeZone can be unavailable on older Windows.
139+
if (!ucal_getTimeZoneIDForWindowsIDRef) {
140+
g_unavailable.store(true, std::memory_order_relaxed);
141+
return Unavailable();
142+
}
143+
144+
// Store the function pointers
145+
g_ucal_getHostTimeZone.store(ucal_getHostTimeZoneRef,
146+
std::memory_order_relaxed);
147+
g_ucal_getTimeZoneIDForWindowsID.store(ucal_getTimeZoneIDForWindowsIDRef,
148+
std::memory_order_relaxed);
149+
150+
return {true, ucal_getHostTimeZoneRef, ucal_getTimeZoneIDForWindowsIDRef};
151+
}
152+
153+
// Convert wchar_t array (UTF-16) to UTF-8 string
154+
std::string Utf16ToUtf8(const wchar_t* ptr, size_t size) {
155+
if (size > std::numeric_limits<int>::max()) {
156+
return std::string();
157+
}
158+
const int chars_len = static_cast<int>(size);
159+
int num_bytes_in_utf8 = 0;
160+
{
161+
const int buffer_size = 32;
162+
char buffer[buffer_size];
163+
num_bytes_in_utf8 =
164+
::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, ptr, chars_len,
165+
buffer, buffer_size, nullptr, nullptr);
166+
if (num_bytes_in_utf8 <= buffer_size) {
167+
return std::string(buffer, num_bytes_in_utf8);
168+
}
169+
}
170+
auto buffer = std::unique_ptr<char[]>(new char[num_bytes_in_utf8]);
171+
const int num_written_bytes =
172+
::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, ptr, chars_len,
173+
buffer.get(), num_bytes_in_utf8, nullptr, nullptr);
174+
if (num_written_bytes != num_bytes_in_utf8) {
175+
return std::string();
176+
}
177+
return std::string(buffer.get(), num_written_bytes);
178+
}
179+
180+
} // namespace
181+
182+
std::string GetWindowsLocalTimeZone() {
183+
const auto icu = GetIcuFunctions();
184+
if (!icu.available) {
185+
return std::string();
186+
}
187+
188+
std::int32_t length = 0;
189+
190+
UErrorCode status = U_ZERO_ERROR;
191+
192+
// Try ucal_getHostTimeZone first (available on Windows 11+)
193+
if (icu.ucal_getHostTimeZone != nullptr) {
194+
const int buffer_size = 32;
195+
UChar buffer[buffer_size];
196+
length = icu.ucal_getHostTimeZone(buffer, buffer_size, &status);
197+
if (U_SUCCESS(status) && length >= 0) {
198+
return Utf16ToUtf8(buffer, length);
199+
}
200+
if (status == U_BUFFER_OVERFLOW_ERROR && length > 0) {
201+
const int buffer_size = length + 1; // +1 for null terminator
202+
auto buffer = std::unique_ptr<UChar[]>(new UChar[buffer_size]);
203+
status = U_ZERO_ERROR;
204+
length = icu.ucal_getHostTimeZone(buffer.get(), buffer_size, &status);
205+
if (U_SUCCESS(status) && length >= 0) {
206+
return Utf16ToUtf8(buffer.get(), length);
207+
}
208+
}
209+
}
210+
211+
DYNAMIC_TIME_ZONE_INFORMATION info = {};
212+
if (::GetDynamicTimeZoneInformation(&info) == TIME_ZONE_ID_INVALID) {
213+
return std::string();
214+
}
215+
{
216+
const int buffer_size = 32;
217+
UChar buffer[buffer_size];
218+
status = U_ZERO_ERROR;
219+
length = icu.ucal_getTimeZoneIDForWindowsID(
220+
info.TimeZoneKeyName, -1, nullptr, buffer, buffer_size, &status);
221+
if (U_SUCCESS(status) && length >= 0) {
222+
return Utf16ToUtf8(buffer, length);
223+
}
224+
if (status != U_BUFFER_OVERFLOW_ERROR || length <= 0) {
225+
return std::string();
226+
}
227+
}
228+
const int buffer_size = length + 1; // +1 for null terminator
229+
auto buffer = std::unique_ptr<UChar[]>(new UChar[buffer_size]);
230+
status = U_ZERO_ERROR;
231+
length = icu.ucal_getTimeZoneIDForWindowsID(
232+
info.TimeZoneKeyName, -1, nullptr, buffer.get(), buffer_size, &status);
233+
if (U_SUCCESS(status) && length >= 0) {
234+
return Utf16ToUtf8(buffer.get(), length);
235+
}
236+
return std::string();
237+
}
238+
239+
} // namespace icu
240+
} // namespace cctz
241+
242+
#endif

src/icu_win.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright 2025 Google Inc. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef CCTZ_ICU_WIN_H_
16+
#define CCTZ_ICU_WIN_H_
17+
18+
#if defined(_WIN32)
19+
#include <string>
20+
21+
namespace cctz {
22+
namespace icu {
23+
24+
std::string GetWindowsLocalTimeZone();
25+
26+
} // namespace icu
27+
} // namespace cctz
28+
29+
#endif // defined(_WIN32)
30+
#endif // CCTZ_ICU_WIN_H_

0 commit comments

Comments
 (0)