Skip to content

Commit 27ca173

Browse files
authored
Reimplement GetWindowsLocalTimeZone without icu.h (#329)
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/time_zone_name_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. * 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 27ca173

File tree

5 files changed

+222
-103
lines changed

5 files changed

+222
-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/time_zone_name_win.cc",
64+
"src/time_zone_name_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+
$<$<PLATFORM_ID:Windows>:src/time_zone_name_win.cc>
89+
$<$<PLATFORM_ID:Windows>:src/time_zone_name_win.h>
8890
${CCTZ_HDRS}
8991
)
9092
cctz_target_set_cxx_standard(cctz)

src/time_zone_lookup.cc

Lines changed: 6 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -30,30 +30,6 @@
3030
#include <zircon/types.h>
3131
#endif
3232

33-
#if defined(_WIN32)
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>)
39-
#define USE_WIN32_LOCAL_TIME_ZONE
40-
#include <windows.h>
41-
#pragma push_macro("_WIN32_WINNT")
42-
#pragma push_macro("NTDDI_VERSION")
43-
// Minimum _WIN32_WINNT and NTDDI_VERSION to use ucal_getTimeZoneIDForWindowsID
44-
#undef _WIN32_WINNT
45-
#define _WIN32_WINNT 0x0A00 // == _WIN32_WINNT_WIN10
46-
#undef NTDDI_VERSION
47-
#define NTDDI_VERSION 0x0A000004 // == NTDDI_WIN10_RS3
48-
#include <icu.h>
49-
#pragma pop_macro("NTDDI_VERSION")
50-
#pragma pop_macro("_WIN32_WINNT")
51-
#include <timezoneapi.h>
52-
#include <atomic>
53-
#endif // __has_include(<icu.h>)
54-
#endif // __has_include
55-
#endif // _WIN32
56-
5733
#include <array>
5834
#include <cstdint>
5935
#include <cstdlib>
@@ -63,83 +39,11 @@
6339
#include "time_zone_fixed.h"
6440
#include "time_zone_impl.h"
6541

66-
namespace cctz {
67-
68-
namespace {
69-
#if defined(USE_WIN32_LOCAL_TIME_ZONE)
70-
// True if we have already failed to load the API.
71-
static std::atomic_bool g_ucal_getTimeZoneIDForWindowsIDUnavailable;
72-
static std::atomic<decltype(ucal_getTimeZoneIDForWindowsID)*>
73-
g_ucal_getTimeZoneIDForWindowsIDRef;
74-
75-
std::string win32_local_time_zone() {
76-
// If we have already failed to load the API, then just give up.
77-
if (g_ucal_getTimeZoneIDForWindowsIDUnavailable.load()) {
78-
return "";
79-
}
80-
81-
auto ucal_getTimeZoneIDForWindowsIDFunc =
82-
g_ucal_getTimeZoneIDForWindowsIDRef.load();
83-
if (ucal_getTimeZoneIDForWindowsIDFunc == nullptr) {
84-
// If we have already failed to load the API, then just give up.
85-
if (g_ucal_getTimeZoneIDForWindowsIDUnavailable.load()) {
86-
return "";
87-
}
88-
89-
const HMODULE icudll = ::LoadLibraryExW(L"icu.dll", nullptr,
90-
LOAD_LIBRARY_SEARCH_SYSTEM32);
91-
92-
if (icudll == nullptr) {
93-
g_ucal_getTimeZoneIDForWindowsIDUnavailable.store(true);
94-
return "";
95-
}
96-
97-
ucal_getTimeZoneIDForWindowsIDFunc =
98-
reinterpret_cast<decltype(ucal_getTimeZoneIDForWindowsID)*>(
99-
::GetProcAddress(icudll, "ucal_getTimeZoneIDForWindowsID"));
100-
101-
if (ucal_getTimeZoneIDForWindowsIDFunc == nullptr) {
102-
g_ucal_getTimeZoneIDForWindowsIDUnavailable.store(true);
103-
return "";
104-
}
105-
// store-race is not a problem here, because ::GetProcAddress() returns the
106-
// same address for the same function in the same DLL.
107-
g_ucal_getTimeZoneIDForWindowsIDRef.store(
108-
ucal_getTimeZoneIDForWindowsIDFunc);
109-
110-
// We intentionally do not call ::FreeLibrary() here to avoid frequent DLL
111-
// loadings and unloading. As "icu.dll" is a system library, keeping it on
112-
// memory is supposed to have no major drawback.
113-
}
114-
115-
DYNAMIC_TIME_ZONE_INFORMATION info = {};
116-
if (::GetDynamicTimeZoneInformation(&info) == TIME_ZONE_ID_INVALID) {
117-
return "";
118-
}
119-
120-
std::array<UChar, 128> buffer;
121-
UErrorCode status = U_ZERO_ERROR;
122-
const auto num_chars_in_buffer = ucal_getTimeZoneIDForWindowsIDFunc(
123-
reinterpret_cast<const UChar*>(info.TimeZoneKeyName), -1, nullptr,
124-
buffer.data(), static_cast<int32_t>(buffer.size()), &status);
125-
if (status != U_ZERO_ERROR || num_chars_in_buffer <= 0 ||
126-
num_chars_in_buffer > static_cast<int32_t>(buffer.size())) {
127-
return "";
128-
}
42+
#if defined(_WIN32)
43+
#include "time_zone_name_win.h"
44+
#endif // _WIN32
12945

130-
const int num_bytes_in_utf8 = ::WideCharToMultiByte(
131-
CP_UTF8, 0, reinterpret_cast<const wchar_t*>(buffer.data()),
132-
static_cast<int>(num_chars_in_buffer), nullptr, 0, nullptr, nullptr);
133-
std::string local_time_str;
134-
local_time_str.resize(static_cast<size_t>(num_bytes_in_utf8));
135-
::WideCharToMultiByte(CP_UTF8, 0, reinterpret_cast<const wchar_t*>(buffer.data()),
136-
static_cast<int>(num_chars_in_buffer),
137-
&local_time_str[0], num_bytes_in_utf8, nullptr,
138-
nullptr);
139-
return local_time_str;
140-
}
141-
#endif // USE_WIN32_LOCAL_TIME_ZONE
142-
} // namespace
46+
namespace cctz {
14347

14448
std::string time_zone::name() const {
14549
return effective_impl().Name();
@@ -259,8 +163,8 @@ time_zone local_time_zone() {
259163
zone = primary_tz.c_str();
260164
}
261165
#endif
262-
#if defined(USE_WIN32_LOCAL_TIME_ZONE)
263-
std::string win32_tz = win32_local_time_zone();
166+
#if defined(_WIN32)
167+
std::string win32_tz = GetWindowsLocalTimeZone();
264168
if (!win32_tz.empty()) {
265169
zone = win32_tz.c_str();
266170
}

src/time_zone_name_win.cc

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
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 "time_zone_name_win.h"
16+
17+
#if !defined(NOMINMAX)
18+
#define NOMINMAX
19+
#endif // !defined(NOMINMAX)
20+
#include <windows.h>
21+
22+
#include <algorithm>
23+
#include <atomic>
24+
#include <cstdint>
25+
#include <limits>
26+
#include <string>
27+
#include <type_traits>
28+
#include <utility>
29+
30+
namespace cctz {
31+
namespace {
32+
33+
// Define UChar as wchar_t here because Win32 APIs receive UTF-16 strings as
34+
// wchar_t* instead of char16_t*. Using char16_t would require additional casts.
35+
using UChar = wchar_t;
36+
37+
enum UErrorCode : std::int32_t {
38+
U_ZERO_ERROR = 0,
39+
U_BUFFER_OVERFLOW_ERROR = 15,
40+
};
41+
42+
bool U_SUCCESS(UErrorCode error) { return error <= U_ZERO_ERROR; }
43+
44+
using ucal_getTimeZoneIDForWindowsID_func = std::int32_t(__cdecl*)(
45+
const UChar* winid, std::int32_t len, const char* region, UChar* id,
46+
std::int32_t id_capacity, UErrorCode* status);
47+
48+
std::atomic<bool> g_unavailable;
49+
std::atomic<ucal_getTimeZoneIDForWindowsID_func>
50+
g_ucal_getTimeZoneIDForWindowsID;
51+
52+
template <typename T> static T AsProcAddress(HMODULE module, const char* name) {
53+
static_assert(
54+
std::is_pointer<T>::value &&
55+
std::is_function<typename std::remove_pointer<T>::type>::value,
56+
"T must be a function pointer type");
57+
const auto proc_address = ::GetProcAddress(module, name);
58+
return reinterpret_cast<T>(reinterpret_cast<void*>(proc_address));
59+
}
60+
61+
std::wstring GetSystem32Dir() {
62+
std::wstring result;
63+
std::uint32_t len = std::max<std::uint32_t>(
64+
static_cast<std::uint32_t>(std::min<size_t>(
65+
result.capacity(), std::numeric_limits<std::uint32_t>::max())),
66+
1);
67+
do {
68+
result.resize(len);
69+
len = ::GetSystemDirectoryW(&result[0], len);
70+
} while (len > result.size());
71+
result.resize(len);
72+
return result;
73+
}
74+
75+
ucal_getTimeZoneIDForWindowsID_func LoadIcuGetTimeZoneIDForWindowsID() {
76+
// This function is intended to be lock free to avoid potential deadlocks
77+
// with loader-lock taken inside LoadLibraryW. As LoadLibraryW and
78+
// GetProcAddress are idempotent unless the DLL is unloaded, we just need to
79+
// make sure global variables are read/written atomically, where
80+
// memory_order_relaxed is also acceptable.
81+
82+
if (g_unavailable.load(std::memory_order_relaxed)) {
83+
return nullptr;
84+
}
85+
86+
{
87+
const auto ucal_getTimeZoneIDForWindowsIDRef =
88+
g_ucal_getTimeZoneIDForWindowsID.load(std::memory_order_relaxed);
89+
if (ucal_getTimeZoneIDForWindowsIDRef != nullptr) {
90+
return ucal_getTimeZoneIDForWindowsIDRef;
91+
}
92+
}
93+
94+
const std::wstring system32_dir = GetSystem32Dir();
95+
if (system32_dir.empty()) {
96+
g_unavailable.store(true, std::memory_order_relaxed);
97+
return nullptr;
98+
}
99+
100+
// Here LoadLibraryExW(L"icu.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32) does
101+
// not work if "icu.dll" is already loaded from somewhere other than the
102+
// system32 directory. Specifying the full path with LoadLibraryW is more
103+
// reliable.
104+
const std::wstring icu_dll_path = system32_dir + L"\\icu.dll";
105+
const HMODULE icu_dll = ::LoadLibraryW(icu_dll_path.c_str());
106+
if (icu_dll == nullptr) {
107+
g_unavailable.store(true, std::memory_order_relaxed);
108+
return nullptr;
109+
}
110+
111+
const auto ucal_getTimeZoneIDForWindowsIDRef =
112+
AsProcAddress<ucal_getTimeZoneIDForWindowsID_func>(
113+
icu_dll, "ucal_getTimeZoneIDForWindowsID");
114+
if (ucal_getTimeZoneIDForWindowsIDRef != nullptr) {
115+
g_unavailable.store(true, std::memory_order_relaxed);
116+
return nullptr;
117+
}
118+
119+
g_ucal_getTimeZoneIDForWindowsID.store(ucal_getTimeZoneIDForWindowsIDRef,
120+
std::memory_order_relaxed);
121+
122+
return ucal_getTimeZoneIDForWindowsIDRef;
123+
}
124+
125+
// Convert wchar_t array (UTF-16) to UTF-8 string
126+
std::string Utf16ToUtf8(const wchar_t* ptr, size_t size) {
127+
if (size > std::numeric_limits<int>::max()) {
128+
return std::string();
129+
}
130+
const int chars_len = static_cast<int>(size);
131+
std::string result;
132+
std::int32_t len = std::max<std::int32_t>(
133+
static_cast<std::int32_t>(std::min<size_t>(
134+
result.capacity(), std::numeric_limits<std::int32_t>::max())),
135+
1);
136+
do {
137+
result.resize(len);
138+
// TODO: Switch to std::string::data() when we require C++17 or higher.
139+
len = ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, ptr, chars_len,
140+
&result[0], len, nullptr, nullptr);
141+
} while (len > result.size());
142+
result.resize(len);
143+
return result;
144+
}
145+
146+
} // namespace
147+
148+
std::string GetWindowsLocalTimeZone() {
149+
const auto getTimeZoneIDForWindowsID = LoadIcuGetTimeZoneIDForWindowsID();
150+
if (getTimeZoneIDForWindowsID == nullptr) {
151+
return std::string();
152+
}
153+
154+
DYNAMIC_TIME_ZONE_INFORMATION info = {};
155+
if (::GetDynamicTimeZoneInformation(&info) == TIME_ZONE_ID_INVALID) {
156+
return std::string();
157+
}
158+
159+
std::wstring result;
160+
std::int32_t len = std::max<std::int32_t>(
161+
static_cast<std::int32_t>(std::min<size_t>(
162+
result.capacity(), std::numeric_limits<std::int32_t>::max())),
163+
1);
164+
for (;;) {
165+
UErrorCode status = U_ZERO_ERROR;
166+
result.resize(len);
167+
len = getTimeZoneIDForWindowsID(info.TimeZoneKeyName, -1, nullptr,
168+
&result[0], len, &status);
169+
if (U_SUCCESS(status)) {
170+
return Utf16ToUtf8(result.data(), len);
171+
}
172+
if (status != U_BUFFER_OVERFLOW_ERROR) {
173+
return std::string();
174+
}
175+
}
176+
}
177+
178+
} // namespace cctz

src/time_zone_name_win.h

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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_TIME_ZONE_NAME_WIN_H_
16+
#define CCTZ_TIME_ZONE_NAME_WIN_H_
17+
18+
#include <string>
19+
20+
namespace cctz {
21+
22+
// Returns the local time zone ID in IANA format (e.g. "America/Los_Angeles"),
23+
// or the empty string on failure. Not supported on Windows 10 1809 and earlier,
24+
// where "icu.dll" is not available in the System32 directory.
25+
std::string GetWindowsLocalTimeZone();
26+
27+
} // namespace cctz
28+
29+
#endif // CCTZ_TIME_ZONE_NAME_WIN_H_

0 commit comments

Comments
 (0)