Skip to content

Commit d47ce9c

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/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 d47ce9c

File tree

5 files changed

+236
-103
lines changed

5 files changed

+236
-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+
$<IF:$<BOOL:WIN32>,src/time_zone_name_win.cc,>
89+
$<IF:$<BOOL:WIN32>,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: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
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 <array>
23+
#include <atomic>
24+
#include <cstdint>
25+
#include <limits>
26+
#include <memory>
27+
#include <string>
28+
#include <type_traits>
29+
#include <utility>
30+
31+
namespace cctz {
32+
namespace {
33+
34+
// UChar is defined as char16_t in ICU by default, but it is also safe to assume
35+
// wchar_t and char16_t are equivalent on Windows.
36+
using UChar = wchar_t;
37+
38+
enum UErrorCode : std::int32_t {
39+
U_ZERO_ERROR = 0,
40+
U_BUFFER_OVERFLOW_ERROR = 15,
41+
};
42+
43+
bool U_SUCCESS(UErrorCode error) { return error <= U_ZERO_ERROR; }
44+
45+
using ucal_getTimeZoneIDForWindowsID_func = std::int32_t(__cdecl*)(
46+
const UChar* winid, std::int32_t len, const char* region, UChar* id,
47+
std::int32_t id_capacity, UErrorCode* status);
48+
49+
std::atomic<bool> g_unavailable;
50+
std::atomic<ucal_getTimeZoneIDForWindowsID_func>
51+
g_ucal_getTimeZoneIDForWindowsID;
52+
53+
struct IcuFunctions {
54+
const ucal_getTimeZoneIDForWindowsID_func ucal_getTimeZoneIDForWindowsID;
55+
};
56+
57+
IcuFunctions Unavailable() { return {nullptr}; }
58+
59+
template <typename T>
60+
static T AsProcAddress(HMODULE module, const char* name) {
61+
static_assert(
62+
std::is_pointer<T>::value &&
63+
std::is_function<typename std::remove_pointer<T>::type>::value,
64+
"T must be a function pointer type");
65+
const auto proc_address = ::GetProcAddress(module, name);
66+
return reinterpret_cast<T>(reinterpret_cast<void*>(proc_address));
67+
}
68+
69+
std::wstring GetSystem32Dir() {
70+
const UINT length = ::GetSystemDirectoryW(nullptr, 0);
71+
if (length == 0) {
72+
// This must not happen, which doesn't look to be recoverable.
73+
return std::wstring();
74+
}
75+
std::unique_ptr<wchar_t[]> buffer(new wchar_t[length]);
76+
const UINT length_written = ::GetSystemDirectoryW(buffer.get(), length);
77+
return std::wstring(buffer.get(), length_written);
78+
}
79+
80+
IcuFunctions GetIcuFunctions() {
81+
// This function is intended to be lock free to avoid potential deadlocks
82+
// with loader-lock taken inside LoadLibraryW. As LoadLibraryW and
83+
// GetProcAddress are idempotent unless the DLL is unloaded, we just need to
84+
// make sure global variables are read/written atomically, where
85+
// memory_order_relaxed is also acceptable.
86+
87+
if (g_unavailable.load(std::memory_order_relaxed)) {
88+
return Unavailable();
89+
}
90+
91+
{
92+
const auto ucal_getTimeZoneIDForWindowsIDRef =
93+
g_ucal_getTimeZoneIDForWindowsID.load(std::memory_order_relaxed);
94+
if (ucal_getTimeZoneIDForWindowsIDRef != nullptr) {
95+
return {ucal_getTimeZoneIDForWindowsIDRef};
96+
}
97+
}
98+
99+
const std::wstring system32_dir = GetSystem32Dir();
100+
if (system32_dir.empty()) {
101+
g_unavailable.store(true, std::memory_order_relaxed);
102+
return Unavailable();
103+
}
104+
105+
// Here LoadLibraryExW(L"icu.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32) does
106+
// not work if "icu.dll" is already loaded from somewhere other than the
107+
// system32 directory. Specifying the full path with LoadLibraryW is more
108+
// reliable.
109+
const std::wstring icu_dll_path = system32_dir + L"\\icu.dll";
110+
const HMODULE icu_dll = ::LoadLibraryW(icu_dll_path.c_str());
111+
if (icu_dll == nullptr) {
112+
g_unavailable.store(true, std::memory_order_relaxed);
113+
return Unavailable();
114+
}
115+
116+
const auto ucal_getTimeZoneIDForWindowsIDRef =
117+
AsProcAddress<ucal_getTimeZoneIDForWindowsID_func>(
118+
icu_dll, "ucal_getTimeZoneIDForWindowsID");
119+
if (!ucal_getTimeZoneIDForWindowsIDRef) {
120+
g_unavailable.store(true, std::memory_order_relaxed);
121+
return Unavailable();
122+
}
123+
124+
g_ucal_getTimeZoneIDForWindowsID.store(ucal_getTimeZoneIDForWindowsIDRef,
125+
std::memory_order_relaxed);
126+
127+
return {ucal_getTimeZoneIDForWindowsIDRef};
128+
}
129+
130+
// Convert wchar_t array (UTF-16) to UTF-8 string
131+
std::string Utf16ToUtf8(const wchar_t* ptr, size_t size) {
132+
if (size > std::numeric_limits<int>::max()) {
133+
return std::string();
134+
}
135+
const int chars_len = static_cast<int>(size);
136+
int num_bytes = 0;
137+
{
138+
std::array<char, 32> buffer;
139+
num_bytes =
140+
::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, ptr, chars_len,
141+
buffer.data(), buffer.size(), nullptr, nullptr);
142+
if (num_bytes <= buffer.size()) {
143+
return std::string(buffer.data(), num_bytes);
144+
}
145+
}
146+
auto buffer = std::unique_ptr<char[]>(new char[num_bytes]);
147+
const int num_written_bytes =
148+
::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, ptr, chars_len,
149+
buffer.get(), num_bytes, nullptr, nullptr);
150+
if (num_written_bytes != num_bytes) {
151+
return std::string();
152+
}
153+
return std::string(buffer.get(), num_written_bytes);
154+
}
155+
156+
} // namespace
157+
158+
std::string GetWindowsLocalTimeZone() {
159+
const auto icu = GetIcuFunctions();
160+
if (icu.ucal_getTimeZoneIDForWindowsID == nullptr) {
161+
return std::string();
162+
}
163+
164+
DYNAMIC_TIME_ZONE_INFORMATION info = {};
165+
if (::GetDynamicTimeZoneInformation(&info) == TIME_ZONE_ID_INVALID) {
166+
return std::string();
167+
}
168+
169+
std::int32_t length = 0;
170+
{
171+
// 32-character is just a guess, but hopefully long enough as a fast-path.
172+
std::array<UChar, 32> buffer;
173+
UErrorCode status = U_ZERO_ERROR;
174+
length = icu.ucal_getTimeZoneIDForWindowsID(info.TimeZoneKeyName, -1,
175+
nullptr, buffer.data(),
176+
buffer.size(), &status);
177+
if (U_SUCCESS(status)) {
178+
return Utf16ToUtf8(buffer.data(), length);
179+
}
180+
if (status != U_BUFFER_OVERFLOW_ERROR) {
181+
return std::string();
182+
}
183+
}
184+
const int buffer_size = length + 1; // +1 for null terminator
185+
auto buffer = std::unique_ptr<UChar[]>(new UChar[buffer_size]);
186+
UErrorCode status = U_ZERO_ERROR;
187+
length = icu.ucal_getTimeZoneIDForWindowsID(
188+
info.TimeZoneKeyName, -1, nullptr, buffer.get(), buffer_size, &status);
189+
if (U_SUCCESS(status)) {
190+
return Utf16ToUtf8(buffer.get(), length);
191+
}
192+
return std::string();
193+
}
194+
195+
} // namespace cctz

src/time_zone_name_win.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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+
std::string GetWindowsLocalTimeZone();
23+
24+
} // namespace cctz
25+
26+
#endif // CCTZ_TIME_ZONE_NAME_WIN_H_

0 commit comments

Comments
 (0)