Import of CCTZ from GitHub. PiperOrigin-RevId: 833899408 Change-Id: I5b6ee41676a8e0e207462e0b30dde3843478ef19
diff --git a/CMake/AbseilDll.cmake b/CMake/AbseilDll.cmake index c49d621..9145d8b 100644 --- a/CMake/AbseilDll.cmake +++ b/CMake/AbseilDll.cmake
@@ -448,7 +448,12 @@ "debugging/leak_check.cc" ) -if(NOT MSVC) +if(MSVC) + list(APPEND ABSL_INTERNAL_DLL_FILES + "time/internal/cctz/src/time_zone_name_win.cc" + "time/internal/cctz/src/time_zone_name_win.h" + ) +else() list(APPEND ABSL_INTERNAL_DLL_FILES "flags/commandlineflag.cc" "flags/commandlineflag.h"
diff --git a/absl/time/CMakeLists.txt b/absl/time/CMakeLists.txt index ea91ba3..34a5ad4 100644 --- a/absl/time/CMakeLists.txt +++ b/absl/time/CMakeLists.txt
@@ -77,6 +77,8 @@ "internal/cctz/src/time_zone_posix.h" "internal/cctz/src/tzfile.h" "internal/cctz/src/zone_info_source.cc" + $<$<PLATFORM_ID:Windows>:internal/cctz/src/time_zone_name_win.cc> + $<$<PLATFORM_ID:Windows>:internal/cctz/src/time_zone_name_win.h> COPTS ${ABSL_DEFAULT_COPTS} DEPS
diff --git a/absl/time/internal/cctz/BUILD.bazel b/absl/time/internal/cctz/BUILD.bazel index 6e17874..e7e2ee0 100644 --- a/absl/time/internal/cctz/BUILD.bazel +++ b/absl/time/internal/cctz/BUILD.bazel
@@ -59,7 +59,13 @@ "src/time_zone_posix.h", "src/tzfile.h", "src/zone_info_source.cc", - ], + ] + select({ + "@platforms//os:windows": [ + "src/time_zone_name_win.cc", + "src/time_zone_name_win.h", + ], + "//conditions:default": [], + }), hdrs = [ "include/cctz/time_zone.h", "include/cctz/zone_info_source.h",
diff --git a/absl/time/internal/cctz/src/time_zone_lookup.cc b/absl/time/internal/cctz/src/time_zone_lookup.cc index e8f1d93..d1078de 100644 --- a/absl/time/internal/cctz/src/time_zone_lookup.cc +++ b/absl/time/internal/cctz/src/time_zone_lookup.cc
@@ -32,31 +32,6 @@ #include <zircon/types.h> #endif -#if defined(_WIN32) -// Include only when <icu.h> is available. -// https://learn.microsoft.com/en-us/windows/win32/intl/international-components-for-unicode--icu- -// https://devblogs.microsoft.com/oldnewthing/20210527-00/?p=105255 -#if defined(__has_include) -#if __has_include(<icu.h>) -#define USE_WIN32_LOCAL_TIME_ZONE -#include <windows.h> -#pragma push_macro("_WIN32_WINNT") -#pragma push_macro("NTDDI_VERSION") -// Minimum _WIN32_WINNT and NTDDI_VERSION to use ucal_getTimeZoneIDForWindowsID -#undef _WIN32_WINNT -#define _WIN32_WINNT 0x0A00 // == _WIN32_WINNT_WIN10 -#undef NTDDI_VERSION -#define NTDDI_VERSION 0x0A000004 // == NTDDI_WIN10_RS3 -#include <icu.h> -#pragma pop_macro("NTDDI_VERSION") -#pragma pop_macro("_WIN32_WINNT") -#include <timezoneapi.h> - -#include <atomic> -#endif // __has_include(<icu.h>) -#endif // __has_include -#endif // _WIN32 - #include <array> #include <cstdint> #include <cstdlib> @@ -66,87 +41,15 @@ #include "absl/time/internal/cctz/src/time_zone_fixed.h" #include "absl/time/internal/cctz/src/time_zone_impl.h" +#if defined(_WIN32) +#include "absl/time/internal/cctz/src/time_zone_name_win.h" +#endif // _WIN32 + namespace absl { ABSL_NAMESPACE_BEGIN namespace time_internal { namespace cctz { -namespace { -#if defined(USE_WIN32_LOCAL_TIME_ZONE) -// True if we have already failed to load the API. -static std::atomic_bool g_ucal_getTimeZoneIDForWindowsIDUnavailable; -static std::atomic<decltype(ucal_getTimeZoneIDForWindowsID)*> - g_ucal_getTimeZoneIDForWindowsIDRef; - -std::string win32_local_time_zone() { - // If we have already failed to load the API, then just give up. - if (g_ucal_getTimeZoneIDForWindowsIDUnavailable.load()) { - return ""; - } - - auto ucal_getTimeZoneIDForWindowsIDFunc = - g_ucal_getTimeZoneIDForWindowsIDRef.load(); - if (ucal_getTimeZoneIDForWindowsIDFunc == nullptr) { - // If we have already failed to load the API, then just give up. - if (g_ucal_getTimeZoneIDForWindowsIDUnavailable.load()) { - return ""; - } - - const HMODULE icudll = - ::LoadLibraryExW(L"icu.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); - - if (icudll == nullptr) { - g_ucal_getTimeZoneIDForWindowsIDUnavailable.store(true); - return ""; - } - - ucal_getTimeZoneIDForWindowsIDFunc = - reinterpret_cast<decltype(ucal_getTimeZoneIDForWindowsID)*>( - ::GetProcAddress(icudll, "ucal_getTimeZoneIDForWindowsID")); - - if (ucal_getTimeZoneIDForWindowsIDFunc == nullptr) { - g_ucal_getTimeZoneIDForWindowsIDUnavailable.store(true); - return ""; - } - // store-race is not a problem here, because ::GetProcAddress() returns the - // same address for the same function in the same DLL. - g_ucal_getTimeZoneIDForWindowsIDRef.store( - ucal_getTimeZoneIDForWindowsIDFunc); - - // We intentionally do not call ::FreeLibrary() here to avoid frequent DLL - // loadings and unloading. As "icu.dll" is a system library, keeping it on - // memory is supposed to have no major drawback. - } - - DYNAMIC_TIME_ZONE_INFORMATION info = {}; - if (::GetDynamicTimeZoneInformation(&info) == TIME_ZONE_ID_INVALID) { - return ""; - } - - std::array<UChar, 128> buffer; - UErrorCode status = U_ZERO_ERROR; - const auto num_chars_in_buffer = ucal_getTimeZoneIDForWindowsIDFunc( - reinterpret_cast<const UChar*>(info.TimeZoneKeyName), -1, nullptr, - buffer.data(), static_cast<int32_t>(buffer.size()), &status); - if (status != U_ZERO_ERROR || num_chars_in_buffer <= 0 || - num_chars_in_buffer > static_cast<int32_t>(buffer.size())) { - return ""; - } - - const int num_bytes_in_utf8 = ::WideCharToMultiByte( - CP_UTF8, 0, reinterpret_cast<const wchar_t*>(buffer.data()), - static_cast<int>(num_chars_in_buffer), nullptr, 0, nullptr, nullptr); - std::string local_time_str; - local_time_str.resize(static_cast<size_t>(num_bytes_in_utf8)); - ::WideCharToMultiByte( - CP_UTF8, 0, reinterpret_cast<const wchar_t*>(buffer.data()), - static_cast<int>(num_chars_in_buffer), &local_time_str[0], - num_bytes_in_utf8, nullptr, nullptr); - return local_time_str; -} -#endif // USE_WIN32_LOCAL_TIME_ZONE -} // namespace - std::string time_zone::name() const { return effective_impl().Name(); } time_zone::absolute_lookup time_zone::lookup( @@ -261,8 +164,8 @@ zone = primary_tz.c_str(); } #endif -#if defined(USE_WIN32_LOCAL_TIME_ZONE) - std::string win32_tz = win32_local_time_zone(); +#if defined(_WIN32) + std::string win32_tz = GetWindowsLocalTimeZone(); if (!win32_tz.empty()) { zone = win32_tz.c_str(); }
diff --git a/absl/time/internal/cctz/src/time_zone_name_win.cc b/absl/time/internal/cctz/src/time_zone_name_win.cc new file mode 100644 index 0000000..c3351cf --- /dev/null +++ b/absl/time/internal/cctz/src/time_zone_name_win.cc
@@ -0,0 +1,187 @@ +// Copyright 2025 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/time/internal/cctz/src/time_zone_name_win.h" + +#include "absl/base/config.h" + +#if !defined(NOMINMAX) +#define NOMINMAX +#endif // !defined(NOMINMAX) +#include <windows.h> + +#include <algorithm> +#include <atomic> +#include <cstdint> +#include <limits> +#include <string> +#include <type_traits> +#include <utility> + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace time_internal { +namespace cctz { +namespace { + +// Define UChar as wchar_t here because Win32 APIs receive UTF-16 strings as +// wchar_t* instead of char16_t*. Using char16_t would require additional casts. +using UChar = wchar_t; + +enum UErrorCode : std::int32_t { + U_ZERO_ERROR = 0, + U_BUFFER_OVERFLOW_ERROR = 15, +}; + +bool U_SUCCESS(UErrorCode error) { return error <= U_ZERO_ERROR; } + +using ucal_getTimeZoneIDForWindowsID_func = std::int32_t(__cdecl*)( + const UChar* winid, std::int32_t len, const char* region, UChar* id, + std::int32_t id_capacity, UErrorCode* status); + +std::atomic<bool> g_unavailable; +std::atomic<ucal_getTimeZoneIDForWindowsID_func> + g_ucal_getTimeZoneIDForWindowsID; + +template <typename T> +static T AsProcAddress(HMODULE module, const char* name) { + static_assert( + std::is_pointer<T>::value && + std::is_function<typename std::remove_pointer<T>::type>::value, + "T must be a function pointer type"); + const auto proc_address = ::GetProcAddress(module, name); + return reinterpret_cast<T>(reinterpret_cast<void*>(proc_address)); +} + +std::wstring GetSystem32Dir() { + std::wstring result; + std::uint32_t len = std::max<std::uint32_t>( + static_cast<std::uint32_t>(std::min<size_t>( + result.capacity(), std::numeric_limits<std::uint32_t>::max())), + 1); + do { + result.resize(len); + len = ::GetSystemDirectoryW(&result[0], len); + } while (len > result.size()); + result.resize(len); + return result; +} + +ucal_getTimeZoneIDForWindowsID_func LoadIcuGetTimeZoneIDForWindowsID() { + // This function is intended to be lock free to avoid potential deadlocks + // with loader-lock taken inside LoadLibraryW. As LoadLibraryW and + // GetProcAddress are idempotent unless the DLL is unloaded, we just need to + // make sure global variables are read/written atomically, where + // memory_order_relaxed is also acceptable. + + if (g_unavailable.load(std::memory_order_relaxed)) { + return nullptr; + } + + { + const auto ucal_getTimeZoneIDForWindowsIDRef = + g_ucal_getTimeZoneIDForWindowsID.load(std::memory_order_relaxed); + if (ucal_getTimeZoneIDForWindowsIDRef != nullptr) { + return ucal_getTimeZoneIDForWindowsIDRef; + } + } + + const std::wstring system32_dir = GetSystem32Dir(); + if (system32_dir.empty()) { + g_unavailable.store(true, std::memory_order_relaxed); + return nullptr; + } + + // Here LoadLibraryExW(L"icu.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32) does + // not work if "icu.dll" is already loaded from somewhere other than the + // system32 directory. Specifying the full path with LoadLibraryW is more + // reliable. + const std::wstring icu_dll_path = system32_dir + L"\\icu.dll"; + const HMODULE icu_dll = ::LoadLibraryW(icu_dll_path.c_str()); + if (icu_dll == nullptr) { + g_unavailable.store(true, std::memory_order_relaxed); + return nullptr; + } + + const auto ucal_getTimeZoneIDForWindowsIDRef = + AsProcAddress<ucal_getTimeZoneIDForWindowsID_func>( + icu_dll, "ucal_getTimeZoneIDForWindowsID"); + if (ucal_getTimeZoneIDForWindowsIDRef != nullptr) { + g_unavailable.store(true, std::memory_order_relaxed); + return nullptr; + } + + g_ucal_getTimeZoneIDForWindowsID.store(ucal_getTimeZoneIDForWindowsIDRef, + std::memory_order_relaxed); + + return ucal_getTimeZoneIDForWindowsIDRef; +} + +// Convert wchar_t array (UTF-16) to UTF-8 string +std::string Utf16ToUtf8(const wchar_t* ptr, size_t size) { + if (size > std::numeric_limits<int>::max()) { + return std::string(); + } + const int chars_len = static_cast<int>(size); + std::string result; + std::int32_t len = std::max<std::int32_t>( + static_cast<std::int32_t>(std::min<size_t>( + result.capacity(), std::numeric_limits<std::int32_t>::max())), + 1); + do { + result.resize(len); + // TODO: Switch to std::string::data() when we require C++17 or higher. + len = ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, ptr, chars_len, + &result[0], len, nullptr, nullptr); + } while (len > result.size()); + result.resize(len); + return result; +} + +} // namespace + +std::string GetWindowsLocalTimeZone() { + const auto getTimeZoneIDForWindowsID = LoadIcuGetTimeZoneIDForWindowsID(); + if (getTimeZoneIDForWindowsID == nullptr) { + return std::string(); + } + + DYNAMIC_TIME_ZONE_INFORMATION info = {}; + if (::GetDynamicTimeZoneInformation(&info) == TIME_ZONE_ID_INVALID) { + return std::string(); + } + + std::wstring result; + std::int32_t len = std::max<std::int32_t>( + static_cast<std::int32_t>(std::min<size_t>( + result.capacity(), std::numeric_limits<std::int32_t>::max())), + 1); + for (;;) { + UErrorCode status = U_ZERO_ERROR; + result.resize(len); + len = getTimeZoneIDForWindowsID(info.TimeZoneKeyName, -1, nullptr, + &result[0], len, &status); + if (U_SUCCESS(status)) { + return Utf16ToUtf8(result.data(), len); + } + if (status != U_BUFFER_OVERFLOW_ERROR) { + return std::string(); + } + } +} + +} // namespace cctz +} // namespace time_internal +ABSL_NAMESPACE_END +} // namespace absl
diff --git a/absl/time/internal/cctz/src/time_zone_name_win.h b/absl/time/internal/cctz/src/time_zone_name_win.h new file mode 100644 index 0000000..f53b5a9 --- /dev/null +++ b/absl/time/internal/cctz/src/time_zone_name_win.h
@@ -0,0 +1,37 @@ +// Copyright 2025 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ABSL_TIME_INTERNAL_CCTZ_TIME_ZONE_NAME_WIN_H_ +#define ABSL_TIME_INTERNAL_CCTZ_TIME_ZONE_NAME_WIN_H_ + +#include <string> + +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace time_internal { +namespace cctz { + +// Returns the local time zone ID in IANA format (e.g. "America/Los_Angeles"), +// or the empty string on failure. Not supported on Windows 10 1809 and earlier, +// where "icu.dll" is not available in the System32 directory. +std::string GetWindowsLocalTimeZone(); + +} // namespace cctz +} // namespace time_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_TIME_INTERNAL_CCTZ_TIME_ZONE_NAME_WIN_H_