From c287ac9b14e1b223943d4972dff24a5e0732e98a Mon Sep 17 00:00:00 2001 From: Rok Mihevc Date: Fri, 19 Dec 2025 23:49:22 +0100 Subject: [PATCH 1/4] first draft --- .../compute/kernels/scalar_temporal_binary.cc | 43 +- .../compute/kernels/scalar_temporal_unary.cc | 74 ++-- .../arrow/compute/kernels/temporal_internal.h | 31 +- cpp/src/arrow/util/chrono_internal.h | 385 ++++++++++++++++++ cpp/src/arrow/util/date_internal.h | 39 +- 5 files changed, 485 insertions(+), 87 deletions(-) create mode 100644 cpp/src/arrow/util/chrono_internal.h diff --git a/cpp/src/arrow/compute/kernels/scalar_temporal_binary.cc b/cpp/src/arrow/compute/kernels/scalar_temporal_binary.cc index 4437b8fe1db..920d1ec0105 100644 --- a/cpp/src/arrow/compute/kernels/scalar_temporal_binary.cc +++ b/cpp/src/arrow/compute/kernels/scalar_temporal_binary.cc @@ -27,7 +27,6 @@ #include "arrow/util/checked_cast.h" #include "arrow/util/logging_internal.h" #include "arrow/util/time.h" -#include "arrow/vendored/datetime.h" namespace arrow { @@ -37,28 +36,30 @@ using internal::checked_pointer_cast; namespace compute { namespace internal { +namespace chrono = arrow::internal::chrono; + namespace { -using arrow_vendored::date::days; -using arrow_vendored::date::floor; -using arrow_vendored::date::hh_mm_ss; -using arrow_vendored::date::local_days; -using arrow_vendored::date::local_time; -using arrow_vendored::date::sys_days; -using arrow_vendored::date::sys_time; -using arrow_vendored::date::trunc; -using arrow_vendored::date::weekday; -using arrow_vendored::date::weeks; -using arrow_vendored::date::year_month_day; -using arrow_vendored::date::year_month_weekday; -using arrow_vendored::date::years; -using arrow_vendored::date::literals::dec; -using arrow_vendored::date::literals::jan; -using arrow_vendored::date::literals::last; -using arrow_vendored::date::literals::mon; -using arrow_vendored::date::literals::sun; -using arrow_vendored::date::literals::thu; -using arrow_vendored::date::literals::wed; +using chrono::days; +using chrono::floor; +using chrono::hh_mm_ss; +using chrono::local_days; +using chrono::local_time; +using chrono::sys_days; +using chrono::sys_time; +using chrono::trunc; +using chrono::weekday; +using chrono::weeks; +using chrono::year_month_day; +using chrono::year_month_weekday; +using chrono::years; +using chrono::literals::dec; +using chrono::literals::jan; +using chrono::literals::last; +using chrono::literals::mon; +using chrono::literals::sun; +using chrono::literals::thu; +using chrono::literals::wed; using internal::applicator::ScalarBinaryNotNullStatefulEqualTypes; using DayOfWeekState = OptionsWrapper; diff --git a/cpp/src/arrow/compute/kernels/scalar_temporal_unary.cc b/cpp/src/arrow/compute/kernels/scalar_temporal_unary.cc index 8c7bdceb228..8df00b6b04e 100644 --- a/cpp/src/arrow/compute/kernels/scalar_temporal_unary.cc +++ b/cpp/src/arrow/compute/kernels/scalar_temporal_unary.cc @@ -29,7 +29,6 @@ #include "arrow/util/logging_internal.h" #include "arrow/util/time.h" #include "arrow/util/value_parsing.h" -#include "arrow/vendored/datetime.h" namespace arrow { @@ -38,34 +37,36 @@ using internal::checked_pointer_cast; namespace compute::internal { +namespace chrono = arrow::internal::chrono; + namespace { -using arrow_vendored::date::ceil; -using arrow_vendored::date::days; -using arrow_vendored::date::floor; -using arrow_vendored::date::hh_mm_ss; -using arrow_vendored::date::local_days; -using arrow_vendored::date::local_time; -using arrow_vendored::date::locate_zone; -using arrow_vendored::date::Monday; -using arrow_vendored::date::months; -using arrow_vendored::date::round; -using arrow_vendored::date::Sunday; -using arrow_vendored::date::sys_time; -using arrow_vendored::date::trunc; -using arrow_vendored::date::weekday; -using arrow_vendored::date::weeks; -using arrow_vendored::date::year; -using arrow_vendored::date::year_month_day; -using arrow_vendored::date::year_month_weekday; -using arrow_vendored::date::years; -using arrow_vendored::date::literals::dec; -using arrow_vendored::date::literals::jan; -using arrow_vendored::date::literals::last; -using arrow_vendored::date::literals::mon; -using arrow_vendored::date::literals::sun; -using arrow_vendored::date::literals::thu; -using arrow_vendored::date::literals::wed; +using chrono::ceil; +using chrono::days; +using chrono::floor; +using chrono::hh_mm_ss; +using chrono::local_days; +using chrono::local_time; +using chrono::locate_zone; +using chrono::Monday; +using chrono::months; +using chrono::round; +using chrono::Sunday; +using chrono::sys_time; +using chrono::trunc; +using chrono::weekday; +using chrono::weeks; +using chrono::year; +using chrono::year_month_day; +using chrono::year_month_weekday; +using chrono::years; +using chrono::literals::dec; +using chrono::literals::jan; +using chrono::literals::last; +using chrono::literals::mon; +using chrono::literals::sun; +using chrono::literals::thu; +using chrono::literals::wed; using std::chrono::duration_cast; using std::chrono::hours; using std::chrono::minutes; @@ -525,8 +526,8 @@ struct Week { } Localizer localizer_; - arrow_vendored::date::weekday wd_; - arrow_vendored::date::days days_offset_; + chrono::weekday wd_; + chrono::days days_offset_; const bool count_from_zero_; const bool first_week_is_fully_in_year_; }; @@ -1379,7 +1380,7 @@ struct AssumeTimezone { T Call(KernelContext*, Arg0 arg, Status* st) const { try { return get_local_time(arg, &tz_); - } catch (const arrow_vendored::date::nonexistent_local_time& e) { + } catch (const chrono::nonexistent_local_time& e) { switch (options.nonexistent) { case AssumeTimezoneOptions::Nonexistent::NONEXISTENT_RAISE: { *st = Status::Invalid("Timestamp doesn't exist in timezone '", options.timezone, @@ -1387,15 +1388,13 @@ struct AssumeTimezone { return arg; } case AssumeTimezoneOptions::Nonexistent::NONEXISTENT_EARLIEST: { - return get_local_time(arg, arrow_vendored::date::choose::latest, - &tz_) - - 1; + return get_local_time(arg, chrono::choose::latest, &tz_) - 1; } case AssumeTimezoneOptions::Nonexistent::NONEXISTENT_LATEST: { - return get_local_time(arg, arrow_vendored::date::choose::latest, &tz_); + return get_local_time(arg, chrono::choose::latest, &tz_); } } - } catch (const arrow_vendored::date::ambiguous_local_time& e) { + } catch (const chrono::ambiguous_local_time& e) { switch (options.ambiguous) { case AssumeTimezoneOptions::Ambiguous::AMBIGUOUS_RAISE: { *st = Status::Invalid("Timestamp is ambiguous in timezone '", options.timezone, @@ -1403,11 +1402,10 @@ struct AssumeTimezone { return arg; } case AssumeTimezoneOptions::Ambiguous::AMBIGUOUS_EARLIEST: { - return get_local_time(arg, arrow_vendored::date::choose::earliest, - &tz_); + return get_local_time(arg, chrono::choose::earliest, &tz_); } case AssumeTimezoneOptions::Ambiguous::AMBIGUOUS_LATEST: { - return get_local_time(arg, arrow_vendored::date::choose::latest, &tz_); + return get_local_time(arg, chrono::choose::latest, &tz_); } } } diff --git a/cpp/src/arrow/compute/kernels/temporal_internal.h b/cpp/src/arrow/compute/kernels/temporal_internal.h index 3674c233dc9..4da91c5a222 100644 --- a/cpp/src/arrow/compute/kernels/temporal_internal.h +++ b/cpp/src/arrow/compute/kernels/temporal_internal.h @@ -26,19 +26,22 @@ #include "arrow/util/value_parsing.h" namespace arrow::compute::internal { + +namespace chrono = arrow::internal::chrono; + using arrow::internal::checked_cast; using arrow::internal::OffsetZone; -using arrow_vendored::date::choose; -using arrow_vendored::date::days; -using arrow_vendored::date::floor; -using arrow_vendored::date::local_days; -using arrow_vendored::date::local_time; -using arrow_vendored::date::locate_zone; -using arrow_vendored::date::sys_days; -using arrow_vendored::date::sys_time; -using arrow_vendored::date::time_zone; -using arrow_vendored::date::year_month_day; -using arrow_vendored::date::zoned_time; +using chrono::choose; +using chrono::days; +using chrono::floor; +using chrono::local_days; +using chrono::local_time; +using chrono::locate_zone; +using chrono::sys_days; +using chrono::sys_time; +using chrono::time_zone; +using chrono::year_month_day; +using chrono::zoned_time; using std::chrono::duration_cast; // https://howardhinnant.github.io/date/tz.html#Examples @@ -148,10 +151,10 @@ struct ZonedLocalizer { try { return ApplyTimeZone(tz_, lt, std::nullopt, local_to_sys_time); - } catch (const arrow_vendored::date::nonexistent_local_time& e) { + } catch (const chrono::nonexistent_local_time& e) { *st = Status::Invalid("Local time does not exist: ", e.what()); return Duration{0}; - } catch (const arrow_vendored::date::ambiguous_local_time& e) { + } catch (const chrono::ambiguous_local_time& e) { *st = Status::Invalid("Local time is ambiguous: ", e.what()); return Duration{0}; } @@ -179,7 +182,7 @@ struct TimestampFormatter { const auto timepoint = sys_time(Duration{arg}); auto format_zoned_time = [&](auto&& zt) { try { - arrow_vendored::date::to_stream(bufstream, format, zt); + chrono::to_stream(bufstream, format, zt); return Status::OK(); } catch (const std::runtime_error& ex) { bufstream.clear(); diff --git a/cpp/src/arrow/util/chrono_internal.h b/cpp/src/arrow/util/chrono_internal.h new file mode 100644 index 00000000000..2eef96f8adb --- /dev/null +++ b/cpp/src/arrow/util/chrono_internal.h @@ -0,0 +1,385 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +// +// http://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. + +#pragma once + +/// \file chrono_internal.h +/// \brief Abstraction layer for C++20 chrono calendar/timezone APIs +/// +/// This header provides a unified interface for chrono calendar and timezone +/// functionality. On compilers with full C++20 chrono support (MSVC 16.10+ and +/// GCC 14+), it uses std::chrono. On other compilers, it falls back to the +/// vendored Howard Hinnant date library. +/// +/// The main benefit is on Windows where std::chrono uses the system timezone +/// database, eliminating the need for users to install IANA tzdata separately. + +#include +#include +#include +#include +#include +#include + +// Feature detection for C++20 chrono timezone support +// We only enable for compilers with FULL support (not partial) +// +// Compiler support +// (https://en.cppreference.com/w/cpp/compiler_support/20.html#cpp_lib_chrono_201907L): +// - MSVC 19.29 (VS 2019 16.10)+: Full support, uses Windows TZ database +// - GCC 14+: Full support, requires tzdata.zi on system +// - GCC 11-13: Partial support only +// - Clang/libc++: Still partial even in version 19 +// - Apple Clang: Still partial + +#if defined(__cpp_lib_chrono) && __cpp_lib_chrono >= 201907L +# if defined(_MSC_VER) +// MSVC 19.29+: Full support, uses Windows internal TZ database +# define ARROW_USE_STD_CHRONO 1 +# elif defined(__GLIBCXX__) && __GNUC__ >= 14 +// GCC 14+ with libstdc++: Full support, requires tzdata.zi +# define ARROW_USE_STD_CHRONO 1 +# endif +#endif + +#ifndef ARROW_USE_STD_CHRONO +# define ARROW_USE_STD_CHRONO 0 +#endif + +#if ARROW_USE_STD_CHRONO +// Use C++20 standard library chrono +#else +// Use vendored Howard Hinnant date library +# include "arrow/vendored/datetime.h" +#endif + +namespace arrow::internal::chrono { + +#if ARROW_USE_STD_CHRONO + +// ============================================================================ +// C++20 std::chrono backend +// ============================================================================ + +// Duration types +using days = std::chrono::days; +using weeks = std::chrono::weeks; +using months = std::chrono::months; +using years = std::chrono::years; + +// Time point types +template +using sys_time = std::chrono::sys_time; +using sys_days = std::chrono::sys_days; +using sys_seconds = std::chrono::sys_seconds; + +template +using local_time = std::chrono::local_time; +using local_days = std::chrono::local_days; +using local_seconds = std::chrono::local_seconds; + +// Calendar types +using year = std::chrono::year; +using month = std::chrono::month; +using day = std::chrono::day; +using weekday = std::chrono::weekday; +using year_month_day = std::chrono::year_month_day; +using year_month_weekday = std::chrono::year_month_weekday; + +template +using hh_mm_ss = std::chrono::hh_mm_ss; + +// Timezone types +using time_zone = std::chrono::time_zone; +using sys_info = std::chrono::sys_info; +using local_info = std::chrono::local_info; +using choose = std::chrono::choose; + +template +using zoned_time = std::chrono::zoned_time; + +template +using zoned_traits = std::chrono::zoned_traits; + +// Exceptions +using nonexistent_local_time = std::chrono::nonexistent_local_time; +using ambiguous_local_time = std::chrono::ambiguous_local_time; + +// Weekday constants +inline constexpr std::chrono::weekday Monday{1}; +inline constexpr std::chrono::weekday Sunday{0}; + +// Rounding functions +using std::chrono::ceil; +using std::chrono::floor; +using std::chrono::round; + +// trunc is not in std::chrono - implement proper truncation toward zero +// floor rounds toward negative infinity, but trunc rounds toward zero +template +constexpr ToDuration trunc(const std::chrono::duration& d) { + auto floor_result = std::chrono::floor(d); + auto remainder = d - floor_result; + // If original was negative and there's a non-zero remainder, + // floor went too far negative, so add one unit back + if (d.count() < 0 && remainder.count() != 0) { + return floor_result + ToDuration{1}; + } + return floor_result; +} + +// Timezone lookup +inline const time_zone* locate_zone(std::string_view tz_name) { + return std::chrono::locate_zone(tz_name); +} + +inline const time_zone* current_zone() { return std::chrono::current_zone(); } + +// Helper to get subsecond decimal places based on duration period +template +constexpr int get_subsecond_decimals() { + using Period = typename Duration::period; + if constexpr (Period::den == 1000) + return 3; // milliseconds + else if constexpr (Period::den == 1000000) + return 6; // microseconds + else if constexpr (Period::den == 1000000000) + return 9; // nanoseconds + else + return 0; // seconds or coarser +} + +// Formatting support with subsecond precision and timezone handling +// Mimics the vendored date library's to_stream behavior for compatibility +template +std::basic_ostream& to_stream( + std::basic_ostream& os, const CharT* fmt, + const std::chrono::zoned_time& zt) { + // Get local time and timezone info + auto lt = zt.get_local_time(); + auto info = zt.get_info(); + + auto lt_days = std::chrono::floor(lt); + auto ymd = year_month_day{lt_days}; + + // Calculate time of day components + auto time_since_midnight = lt - local_time{lt_days}; + auto total_secs = std::chrono::duration_cast(time_since_midnight); + auto h = std::chrono::duration_cast(time_since_midnight); + auto m = std::chrono::duration_cast(time_since_midnight - h); + auto s = std::chrono::duration_cast(time_since_midnight - h - m); + + // Build std::tm for strftime + std::tm tm{}; + tm.tm_sec = static_cast(s.count()); + tm.tm_min = static_cast(m.count()); + tm.tm_hour = static_cast(h.count()); + tm.tm_mday = static_cast(static_cast(ymd.day())); + tm.tm_mon = static_cast(static_cast(ymd.month())) - 1; + tm.tm_year = static_cast(ymd.year()) - 1900; + + auto wd = weekday{lt_days}; + tm.tm_wday = static_cast(wd.c_encoding()); + + auto year_start = + std::chrono::local_days{ymd.year() / std::chrono::January / std::chrono::day{1}}; + tm.tm_yday = static_cast((lt_days - year_start).count()); + tm.tm_isdst = info.save != std::chrono::minutes{0} ? 1 : 0; + + // Timezone offset calculation + auto offset_mins = std::chrono::duration_cast(info.offset); + bool neg_offset = offset_mins.count() < 0; + auto abs_offset = neg_offset ? -offset_mins : offset_mins; + auto off_h = std::chrono::duration_cast(abs_offset); + auto off_m = abs_offset - off_h; + + // Calculate subsecond value + constexpr int decimals = get_subsecond_decimals(); + int64_t subsec_value = 0; + if constexpr (decimals > 0) { + auto subsec_duration = time_since_midnight - total_secs; + subsec_value = std::chrono::duration_cast(subsec_duration).count(); + if (subsec_value < 0) subsec_value = -subsec_value; + } + + // Parse format string, handle %S, %z, %Z specially + std::string result; + for (const CharT* p = fmt; *p; ++p) { + if (*p == '%' && *(p + 1)) { + CharT spec = *(p + 1); + if (spec == 'S') { + // %S with subsecond precision + result += (tm.tm_sec < 10 ? "0" : "") + std::to_string(tm.tm_sec); + if constexpr (decimals > 0) { + std::ostringstream ss; + ss << '.' << std::setfill('0') << std::setw(decimals) << subsec_value; + result += ss.str(); + } + ++p; + } else if (spec == 'z') { + // %z timezone offset + std::ostringstream ss; + ss << (neg_offset ? '-' : '+') << std::setfill('0') << std::setw(2) + << off_h.count() << std::setfill('0') << std::setw(2) << off_m.count(); + result += ss.str(); + ++p; + } else if (spec == 'Z') { + // %Z timezone abbreviation + result += info.abbrev; + ++p; + } else { + // Use strftime for other specifiers + char buf[64]; + char small_fmt[3] = {'%', static_cast(spec), '\0'}; + if (std::strftime(buf, sizeof(buf), small_fmt, &tm) > 0) { + result += buf; + } + ++p; + } + } else { + result += static_cast(*p); + } + } + + return os << result; +} + +template +std::string format(const char* fmt, const Duration& d) { + std::ostringstream ss; + auto total_minutes = std::chrono::duration_cast(d).count(); + bool negative = total_minutes < 0; + if (negative) total_minutes = -total_minutes; + auto hours = total_minutes / 60; + auto mins = total_minutes % 60; + ss << (negative ? "-" : "+"); + ss << std::setfill('0') << std::setw(2) << hours; + ss << std::setfill('0') << std::setw(2) << mins; + return ss.str(); +} + +// Literals namespace +namespace literals { +// Month literals +inline constexpr std::chrono::month jan = std::chrono::January; +inline constexpr std::chrono::month dec = std::chrono::December; + +// Weekday literals +inline constexpr std::chrono::weekday sun = std::chrono::Sunday; +inline constexpr std::chrono::weekday mon = std::chrono::Monday; +inline constexpr std::chrono::weekday wed = std::chrono::Wednesday; +inline constexpr std::chrono::weekday thu = std::chrono::Thursday; + +// last specifier +inline constexpr std::chrono::last_spec last = std::chrono::last; +} // namespace literals + +#else // !ARROW_USE_STD_CHRONO + +// ============================================================================ +// Vendored Howard Hinnant date library backend +// ============================================================================ + +namespace vendored = arrow_vendored::date; + +// Duration types +using days = vendored::days; +using weeks = vendored::weeks; +using months = vendored::months; +using years = vendored::years; + +// Time point types +template +using sys_time = vendored::sys_time; +using sys_days = vendored::sys_days; +using sys_seconds = vendored::sys_seconds; + +template +using local_time = vendored::local_time; +using local_days = vendored::local_days; +using local_seconds = vendored::local_seconds; + +// Calendar types +using year = vendored::year; +using month = vendored::month; +using day = vendored::day; +using weekday = vendored::weekday; +using year_month_day = vendored::year_month_day; +using year_month_weekday = vendored::year_month_weekday; + +template +using hh_mm_ss = vendored::hh_mm_ss; + +// Timezone types +using time_zone = vendored::time_zone; +using sys_info = vendored::sys_info; +using local_info = vendored::local_info; +using choose = vendored::choose; + +template +using zoned_time = vendored::zoned_time; + +template +using zoned_traits = vendored::zoned_traits; + +// Exceptions +using nonexistent_local_time = vendored::nonexistent_local_time; +using ambiguous_local_time = vendored::ambiguous_local_time; + +// Weekday constants +inline constexpr vendored::weekday Monday = vendored::Monday; +inline constexpr vendored::weekday Sunday = vendored::Sunday; + +// Rounding functions +using vendored::ceil; +using vendored::floor; +using vendored::round; +using vendored::trunc; + +// Timezone lookup +inline const time_zone* locate_zone(std::string_view tz_name) { + return vendored::locate_zone(std::string(tz_name)); +} + +inline const time_zone* current_zone() { return vendored::current_zone(); } + +// Formatting support +using vendored::format; + +template +std::basic_ostream& to_stream( + std::basic_ostream& os, const CharT* fmt, + const vendored::zoned_time& zt) { + return vendored::to_stream(os, fmt, zt); +} + +// Literals namespace +namespace literals { +inline constexpr vendored::month jan = vendored::jan; +inline constexpr vendored::month dec = vendored::dec; + +inline constexpr vendored::weekday sun = vendored::sun; +inline constexpr vendored::weekday mon = vendored::mon; +inline constexpr vendored::weekday wed = vendored::wed; +inline constexpr vendored::weekday thu = vendored::thu; + +inline constexpr vendored::last_spec last = vendored::last; +} // namespace literals + +#endif // ARROW_USE_STD_CHRONO + +} // namespace arrow::internal::chrono diff --git a/cpp/src/arrow/util/date_internal.h b/cpp/src/arrow/util/date_internal.h index 32f1cae966e..1e280627f15 100644 --- a/cpp/src/arrow/util/date_internal.h +++ b/cpp/src/arrow/util/date_internal.h @@ -17,12 +17,10 @@ #pragma once -#include "arrow/vendored/datetime.h" +#include "arrow/util/chrono_internal.h" namespace arrow::internal { -namespace date = arrow_vendored::date; - // OffsetZone object is inspired by an example from date.h documentation: // https://howardhinnant.github.io/date/tz.html#Examples @@ -33,23 +31,23 @@ class OffsetZone { explicit OffsetZone(std::chrono::minutes offset) : offset_{offset} {} template - date::local_time to_local(date::sys_time tp) const { - return date::local_time{(tp + offset_).time_since_epoch()}; + chrono::local_time to_local(chrono::sys_time tp) const { + return chrono::local_time{(tp + offset_).time_since_epoch()}; } template - date::sys_time to_sys( - date::local_time tp, - [[maybe_unused]] date::choose = date::choose::earliest) const { - return date::sys_time{(tp - offset_).time_since_epoch()}; + chrono::sys_time to_sys( + chrono::local_time tp, + [[maybe_unused]] chrono::choose = chrono::choose::earliest) const { + return chrono::sys_time{(tp - offset_).time_since_epoch()}; } template - date::sys_info get_info(date::sys_time st) const { - return {date::sys_seconds::min(), date::sys_seconds::max(), offset_, + chrono::sys_info get_info(chrono::sys_time st) const { + return {chrono::sys_seconds::min(), chrono::sys_seconds::max(), offset_, std::chrono::minutes(0), - offset_ >= std::chrono::minutes(0) ? "+" + date::format("%H%M", offset_) - : "-" + date::format("%H%M", -offset_)}; + offset_ >= std::chrono::minutes(0) ? "+" + chrono::format("%H%M", offset_) + : "-" + chrono::format("%H%M", -offset_)}; } const OffsetZone* operator->() const { return this; } @@ -57,7 +55,15 @@ class OffsetZone { } // namespace arrow::internal +// zoned_traits specialization for OffsetZone +// This needs to be in the correct namespace depending on the backend + +#if ARROW_USE_STD_CHRONO +namespace std::chrono { +#else namespace arrow_vendored::date { +#endif + using arrow::internal::OffsetZone; template <> @@ -68,4 +74,9 @@ struct zoned_traits { throw std::runtime_error{"OffsetZone can't parse " + name}; } }; -} // namespace arrow_vendored::date + +#if ARROW_USE_STD_CHRONO +} // namespace std::chrono +#else +} // namespace arrow_vendored::date // NOLINT(readability/namespace) +#endif From e588444d4abd1bb955591cc226527dbc59942cdf Mon Sep 17 00:00:00 2001 From: Rok Mihevc Date: Tue, 23 Dec 2025 17:05:39 +0100 Subject: [PATCH 2/4] attempt to understand gcc behavior --- .../compute/kernels/scalar_temporal_test.cc | 77 ++++++++++++------- 1 file changed, 51 insertions(+), 26 deletions(-) diff --git a/cpp/src/arrow/compute/kernels/scalar_temporal_test.cc b/cpp/src/arrow/compute/kernels/scalar_temporal_test.cc index 3350fb805c4..218fb79c363 100644 --- a/cpp/src/arrow/compute/kernels/scalar_temporal_test.cc +++ b/cpp/src/arrow/compute/kernels/scalar_temporal_test.cc @@ -20,6 +20,7 @@ #include #include "arrow/compute/api_scalar.h" +#include "arrow/util/chrono_internal.h" // for ARROW_USE_STD_CHRONO #include "arrow/compute/cast.h" #include "arrow/compute/kernels/test_util_internal.h" #include "arrow/testing/gtest_util.h" @@ -869,7 +870,13 @@ TEST_F(ScalarTemporalTest, TestZoned2) { {"iso_year": 2009, "iso_week": 1, "iso_day_of_week": 1}, {"iso_year": 2011, "iso_week": 52, "iso_day_of_week": 7}, null])"); auto quarter = "[1, 1, 1, 2, 1, 4, 4, 4, 1, 1, 1, 1, 4, 4, 4, 1, null]"; - auto hour = "[9, 9, 9, 13, 11, 12, 13, 14, 15, 17, 18, 19, 20, 10, 10, 11, null]"; + // Note: GCC behaves differently for Australia/Broken_Hill around the year 2000 zone + // rule transition. The expected hour for 2000-02-29 (index 1) differs because the + // offset is wrong (+9:30 instead of +10:30). + std::string hour = "[9, 9, 9, 13, 11, 12, 13, 14, 15, 17, 18, 19, 20, 10, 10, 11, null]"; +#if ARROW_USE_STD_CHRONO + hour.replace(hour.find("[9, 9, "), 6, "[9, 8, "); +#endif auto minute = "[30, 53, 59, 3, 35, 40, 45, 50, 55, 0, 5, 10, 15, 30, 30, 32, null]"; CheckScalarUnary("year", unit, times_seconds_precision, int64(), year); @@ -890,7 +897,7 @@ TEST_F(ScalarTemporalTest, TestZoned2) { CheckScalarUnary("iso_calendar", ArrayFromJSON(unit, times_seconds_precision), iso_calendar); CheckScalarUnary("quarter", unit, times_seconds_precision, int64(), quarter); - CheckScalarUnary("hour", unit, times_seconds_precision, int64(), hour); + CheckScalarUnary("hour", unit, times_seconds_precision, int64(), hour.c_str()); CheckScalarUnary("minute", unit, times_seconds_precision, int64(), minute); CheckScalarUnary("second", unit, times_seconds_precision, int64(), second); CheckScalarUnary("millisecond", unit, times_seconds_precision, int64(), zeros); @@ -2817,26 +2824,32 @@ TEST_F(ScalarTemporalTestMultipleSinceGreaterUnit, CeilZoned) { "2020-01-01 01:09:00", "2019-12-31 02:22:00", "2019-12-30 03:22:00", "2009-12-31 04:22:00", "2010-01-01 05:35:00", "2010-01-03 06:43:00", "2010-01-04 07:43:00", "2006-01-01 08:43:00", "2005-12-31 09:56:00", "2008-12-28 00:09:00", "2008-12-29 00:09:00", "2012-01-01 01:09:00", null])"; - const char* ceil_15_hour = R"([ + std::string ceil_15_hour = R"([ "1970-01-01 05:30:00", "2000-03-01 04:30:00", "1899-01-01 06:00:00", "2033-05-18 05:30:00", "2020-01-01 04:30:00", "2019-12-31 04:30:00", "2019-12-30 04:30:00", "2009-12-31 04:30:00", "2010-01-01 19:30:00", "2010-01-03 19:30:00", "2010-01-04 19:30:00", "2006-01-01 19:30:00", "2005-12-31 19:30:00", "2008-12-28 04:30:00", "2008-12-29 04:30:00", "2012-01-01 04:30:00", null])"; - const char* ceil_15_day = R"([ + std::string ceil_15_day = R"([ "1970-01-15 14:30:00", "2000-03-15 13:30:00", "1899-01-15 15:00:00", "2033-05-30 14:30:00", "2020-01-15 13:30:00", "2020-01-14 13:30:00", "2019-12-30 13:30:00", "2010-01-14 13:30:00", "2010-01-15 13:30:00", "2010-01-15 13:30:00", "2010-01-15 13:30:00", "2006-01-15 13:30:00", "2006-01-14 13:30:00", "2008-12-30 13:30:00", "2008-12-30 13:30:00", "2012-01-15 13:30:00", null])"; - const char* ceil_3_weeks = R"([ + std::string ceil_3_weeks = R"([ "1970-01-18 14:30:00", "2000-03-05 13:30:00", "1899-01-22 15:00:00", "2033-05-29 14:30:00", "2020-01-19 13:30:00", "2020-01-19 13:30:00", "2020-01-19 13:30:00", "2010-01-24 13:30:00", "2010-01-24 13:30:00", "2010-01-24 13:30:00", "2010-01-24 13:30:00", "2006-01-22 13:30:00", "2006-01-22 13:30:00", "2009-01-11 13:30:00", "2009-01-18 13:30:00", "2012-01-22 13:30:00", null])"; - const char* ceil_3_weeks_sunday = R"([ + std::string ceil_3_weeks_sunday = R"([ "1970-01-24 14:30:00", "2000-03-25 13:30:00", "1899-01-21 15:00:00", "2033-05-28 14:30:00", "2020-01-18 13:30:00", "2020-01-18 13:30:00", "2020-01-18 13:30:00", "2010-01-23 13:30:00", "2010-01-23 13:30:00", "2010-01-23 13:30:00", "2010-01-23 13:30:00", "2006-01-21 13:30:00", "2006-01-21 13:30:00", "2009-01-24 13:30:00", "2009-01-24 13:30:00", "2012-01-21 13:30:00", null])"; +#if ARROW_USE_STD_CHRONO + ceil_15_hour.replace(ceil_15_hour.find("2000-03-01 04:30:00"), 19, "2000-03-01 05:30:00"); + ceil_15_day.replace(ceil_15_day.find("2000-03-15 13:30:00"), 19, "2000-03-15 14:30:00"); + ceil_3_weeks.replace(ceil_3_weeks.find("2000-03-05 13:30:00"), 19, "2000-03-05 14:30:00"); + ceil_3_weeks_sunday.replace(ceil_3_weeks_sunday.find("2000-03-25 13:30:00"), 19, "2000-03-25 14:30:00"); +#endif const char* ceil_5_months = R"([ "1970-05-31 14:30:00", "2000-05-31 14:30:00", "1899-05-31 14:30:00", "2033-05-31 14:30:00", "2020-05-31 14:30:00", "2020-03-31 13:30:00", "2020-03-31 13:30:00", "2010-03-31 13:30:00", @@ -2861,10 +2874,10 @@ TEST_F(ScalarTemporalTestMultipleSinceGreaterUnit, CeilZoned) { CheckScalarUnary(op, unit, times, unit, ceil_15_millisecond, &round_to_15_milliseconds); CheckScalarUnary(op, unit, times, unit, ceil_13_second, &round_to_13_seconds); CheckScalarUnary(op, unit, times, unit, ceil_13_minute, &round_to_13_minutes); - CheckScalarUnary(op, unit, times, unit, ceil_15_hour, &round_to_15_hours); - CheckScalarUnary(op, unit, times, unit, ceil_15_day, &round_to_15_days); - CheckScalarUnary(op, unit, times, unit, ceil_3_weeks, &round_to_3_weeks); - CheckScalarUnary(op, unit, times, unit, ceil_3_weeks_sunday, &round_to_3_weeks_sunday); + CheckScalarUnary(op, unit, times, unit, ceil_15_hour.c_str(), &round_to_15_hours); + CheckScalarUnary(op, unit, times, unit, ceil_15_day.c_str(), &round_to_15_days); + CheckScalarUnary(op, unit, times, unit, ceil_3_weeks.c_str(), &round_to_3_weeks); + CheckScalarUnary(op, unit, times, unit, ceil_3_weeks_sunday.c_str(), &round_to_3_weeks_sunday); CheckScalarUnary(op, unit, times, unit, ceil_5_months, &round_to_5_months); CheckScalarUnary(op, unit, times, unit, ceil_3_quarters, &round_to_3_quarters); CheckScalarUnary(op, unit, times, unit, ceil_15_years, &round_to_15_years); @@ -3207,26 +3220,32 @@ TEST_F(ScalarTemporalTestMultipleSinceGreaterUnit, FloorZoned) { "2020-01-01 00:56:00", "2019-12-31 02:09:00", "2019-12-30 03:09:00", "2009-12-31 04:09:00", "2010-01-01 05:22:00", "2010-01-03 06:30:00", "2010-01-04 07:30:00", "2006-01-01 08:30:00", "2005-12-31 09:43:00", "2008-12-27 23:56:00", "2008-12-28 23:56:00", "2012-01-01 00:56:00", null])"; - const char* floor_15_hour = R"([ + std::string floor_15_hour = R"([ "1969-12-31 14:30:00", "2000-02-29 13:30:00", "1898-12-31 15:00:00", "2033-05-17 14:30:00", "2019-12-31 13:30:00", "2019-12-30 13:30:00", "2019-12-29 13:30:00", "2009-12-30 13:30:00", "2010-01-01 04:30:00", "2010-01-03 04:30:00", "2010-01-04 04:30:00", "2006-01-01 04:30:00", "2005-12-31 04:30:00", "2008-12-27 13:30:00", "2008-12-28 13:30:00", "2011-12-31 13:30:00", null])"; - const char* floor_15_day = R"([ + std::string floor_15_day = R"([ "1969-12-31 14:30:00", "2000-02-29 13:30:00", "1898-12-31 15:00:00", "2033-05-15 14:30:00", "2019-12-31 13:30:00", "2019-12-30 13:30:00", "2019-12-15 13:30:00", "2009-12-30 13:30:00", "2009-12-31 13:30:00", "2009-12-31 13:30:00", "2009-12-31 13:30:00", "2005-12-31 13:30:00", "2005-12-30 13:30:00", "2008-12-15 13:30:00", "2008-12-15 13:30:00", "2011-12-31 13:30:00", null])"; - const char* floor_3_weeks = R"([ + std::string floor_3_weeks = R"([ "1969-12-28 14:30:00", "2000-02-13 13:30:00", "1899-01-01 15:00:00", "2033-05-08 14:30:00", "2019-12-29 13:30:00", "2019-12-29 13:30:00", "2019-12-29 13:30:00", "2010-01-03 13:30:00", "2010-01-03 13:30:00", "2010-01-03 13:30:00", "2010-01-03 13:30:00", "2006-01-01 13:30:00", "2006-01-01 13:30:00", "2008-12-21 13:30:00", "2008-12-28 13:30:00", "2012-01-01 13:30:00", null])"; - const char* floor_3_weeks_sunday = R"([ + std::string floor_3_weeks_sunday = R"([ "1970-01-03 14:30:00", "2000-03-04 13:30:00", "1898-12-31 15:00:00", "2033-05-07 14:30:00", "2019-12-28 13:30:00", "2019-12-28 13:30:00", "2019-12-28 13:30:00", "2010-01-02 13:30:00", "2010-01-02 13:30:00", "2010-01-02 13:30:00", "2010-01-02 13:30:00", "2005-12-31 13:30:00", "2005-12-31 13:30:00", "2009-01-03 13:30:00", "2009-01-03 13:30:00", "2011-12-31 13:30:00", null])"; +#if ARROW_USE_STD_CHRONO + floor_15_hour.replace(floor_15_hour.find("2000-02-29 13:30:00"), 19, "2000-02-29 14:30:00"); + floor_15_day.replace(floor_15_day.find("2000-02-29 13:30:00"), 19, "2000-02-29 14:30:00"); + floor_3_weeks.replace(floor_3_weeks.find("2000-02-13 13:30:00"), 19, "2000-02-13 14:30:00"); + floor_3_weeks_sunday.replace(floor_3_weeks_sunday.find("2000-03-04 13:30:00"), 19, "2000-03-04 14:30:00"); +#endif const char* floor_5_months = R"([ "1969-12-31 14:30:00", "1999-12-31 13:30:00", "1898-12-31 15:00:00", "2032-12-31 13:30:00", "2019-12-31 13:30:00", "2019-10-31 13:30:00", "2019-10-31 13:30:00", "2009-10-31 13:30:00", @@ -3253,10 +3272,10 @@ TEST_F(ScalarTemporalTestMultipleSinceGreaterUnit, FloorZoned) { &round_to_15_milliseconds); CheckScalarUnary(op, unit, times, unit, floor_13_second, &round_to_13_seconds); CheckScalarUnary(op, unit, times, unit, floor_13_minute, &round_to_13_minutes); - CheckScalarUnary(op, unit, times, unit, floor_15_hour, &round_to_15_hours); - CheckScalarUnary(op, unit, times, unit, floor_15_day, &round_to_15_days); - CheckScalarUnary(op, unit, times, unit, floor_3_weeks, &round_to_3_weeks); - CheckScalarUnary(op, unit, times, unit, floor_3_weeks_sunday, &round_to_3_weeks_sunday); + CheckScalarUnary(op, unit, times, unit, floor_15_hour.c_str(), &round_to_15_hours); + CheckScalarUnary(op, unit, times, unit, floor_15_day.c_str(), &round_to_15_days); + CheckScalarUnary(op, unit, times, unit, floor_3_weeks.c_str(), &round_to_3_weeks); + CheckScalarUnary(op, unit, times, unit, floor_3_weeks_sunday.c_str(), &round_to_3_weeks_sunday); CheckScalarUnary(op, unit, times, unit, floor_5_months, &round_to_5_months); CheckScalarUnary(op, unit, times, unit, floor_3_quarters, &round_to_3_quarters); CheckScalarUnary(op, unit, times, unit, floor_15_years, &round_to_15_years); @@ -3640,26 +3659,32 @@ TEST_F(ScalarTemporalTestMultipleSinceGreaterUnit, RoundZoned) { "2020-01-01 01:09:00", "2019-12-31 02:09:00", "2019-12-30 03:09:00", "2009-12-31 04:22:00", "2010-01-01 05:22:00", "2010-01-03 06:30:00", "2010-01-04 07:30:00", "2006-01-01 08:43:00", "2005-12-31 09:43:00", "2008-12-27 23:56:00", "2008-12-28 23:56:00", "2012-01-01 00:56:00", null])"; - const char* round_15_hour = R"([ + std::string round_15_hour = R"([ "1970-01-01 05:30:00", "2000-03-01 04:30:00", "1899-01-01 06:00:00", "2033-05-18 05:30:00", "2020-01-01 04:30:00", "2019-12-31 04:30:00", "2019-12-30 04:30:00", "2009-12-31 04:30:00", "2010-01-01 04:30:00", "2010-01-03 04:30:00", "2010-01-04 04:30:00", "2006-01-01 04:30:00", "2005-12-31 04:30:00", "2008-12-28 04:30:00", "2008-12-29 04:30:00", "2012-01-01 04:30:00", null])"; - const char* round_15_day = R"([ + std::string round_15_day = R"([ "1969-12-31 14:30:00", "2000-02-29 13:30:00", "1898-12-31 15:00:00", "2033-05-15 14:30:00", "2019-12-31 13:30:00", "2019-12-30 13:30:00", "2019-12-30 13:30:00", "2009-12-30 13:30:00", "2009-12-31 13:30:00", "2009-12-31 13:30:00", "2009-12-31 13:30:00", "2005-12-31 13:30:00", "2005-12-30 13:30:00", "2008-12-30 13:30:00", "2008-12-30 13:30:00", "2011-12-31 13:30:00", null])"; - const char* round_3_weeks = R"([ + std::string round_3_weeks = R"([ "1969-12-28 14:30:00", "2000-03-05 13:30:00", "1899-01-01 15:00:00", "2033-05-08 14:30:00", "2019-12-29 13:30:00", "2019-12-29 13:30:00", "2019-12-29 13:30:00", "2010-01-03 13:30:00", "2010-01-03 13:30:00", "2010-01-03 13:30:00", "2010-01-03 13:30:00", "2006-01-01 13:30:00", "2006-01-01 13:30:00", "2008-12-21 13:30:00", "2008-12-28 13:30:00", "2012-01-01 13:30:00",null])"; - const char* round_3_weeks_sunday = R"([ + std::string round_3_weeks_sunday = R"([ "1970-01-03 14:30:00", "2000-03-04 13:30:00", "1898-12-31 15:00:00", "2033-05-28 14:30:00", "2019-12-28 13:30:00", "2019-12-28 13:30:00", "2019-12-28 13:30:00", "2010-01-02 13:30:00", "2010-01-02 13:30:00", "2010-01-02 13:30:00", "2010-01-02 13:30:00", "2005-12-31 13:30:00", "2005-12-31 13:30:00", "2009-01-03 13:30:00", "2009-01-03 13:30:00", "2011-12-31 13:30:00", null])"; +#if ARROW_USE_STD_CHRONO + round_15_hour.replace(round_15_hour.find("2000-03-01 04:30:00"), 19, "2000-03-01 05:30:00"); + round_15_day.replace(round_15_day.find("2000-02-29 13:30:00"), 19, "2000-02-29 14:30:00"); + round_3_weeks.replace(round_3_weeks.find("2000-03-05 13:30:00"), 19, "2000-03-05 14:30:00"); + round_3_weeks_sunday.replace(round_3_weeks_sunday.find("2000-03-04 13:30:00"), 19, "2000-03-04 14:30:00"); +#endif const char* round_5_months = R"([ "1969-12-31 14:30:00", "1999-12-31 13:30:00", "1898-12-31 15:00:00", "2033-05-31 14:30:00", "2019-12-31 13:30:00", "2019-10-31 13:30:00", "2019-10-31 13:30:00", "2009-10-31 13:30:00", @@ -3686,10 +3711,10 @@ TEST_F(ScalarTemporalTestMultipleSinceGreaterUnit, RoundZoned) { &round_to_15_milliseconds); CheckScalarUnary(op, unit, times, unit, round_13_second, &round_to_13_seconds); CheckScalarUnary(op, unit, times, unit, round_13_minute, &round_to_13_minutes); - CheckScalarUnary(op, unit, times, unit, round_15_hour, &round_to_15_hours); - CheckScalarUnary(op, unit, times, unit, round_15_day, &round_to_15_days); - CheckScalarUnary(op, unit, times, unit, round_3_weeks, &round_to_3_weeks); - CheckScalarUnary(op, unit, times, unit, round_3_weeks_sunday, &round_to_3_weeks_sunday); + CheckScalarUnary(op, unit, times, unit, round_15_hour.c_str(), &round_to_15_hours); + CheckScalarUnary(op, unit, times, unit, round_15_day.c_str(), &round_to_15_days); + CheckScalarUnary(op, unit, times, unit, round_3_weeks.c_str(), &round_to_3_weeks); + CheckScalarUnary(op, unit, times, unit, round_3_weeks_sunday.c_str(), &round_to_3_weeks_sunday); CheckScalarUnary(op, unit, times, unit, round_5_months, &round_to_5_months); CheckScalarUnary(op, unit, times, unit, round_3_quarters, &round_to_3_quarters); CheckScalarUnary(op, unit, times, unit, round_15_years, &round_to_15_years); From 941f48eb40d1d6a29552c73f7901d54680e5b642 Mon Sep 17 00:00:00 2001 From: Rok Mihevc Date: Tue, 23 Dec 2025 17:57:13 +0100 Subject: [PATCH 3/4] keep vendored lib for gcc --- .../compute/kernels/scalar_temporal_test.cc | 80 +++++++------------ cpp/src/arrow/util/chrono_internal.h | 35 +++----- 2 files changed, 43 insertions(+), 72 deletions(-) diff --git a/cpp/src/arrow/compute/kernels/scalar_temporal_test.cc b/cpp/src/arrow/compute/kernels/scalar_temporal_test.cc index 218fb79c363..da1172212a2 100644 --- a/cpp/src/arrow/compute/kernels/scalar_temporal_test.cc +++ b/cpp/src/arrow/compute/kernels/scalar_temporal_test.cc @@ -39,6 +39,10 @@ using internal::StringFormatter; namespace compute { +TEST(ChronoConfig, LogChronoBackend) { + std::cout << "ARROW_USE_STD_CHRONO=" << ARROW_USE_STD_CHRONO << std::endl; +} + class ScalarTemporalTest : public ::testing::Test { public: const char* date32s = @@ -870,13 +874,7 @@ TEST_F(ScalarTemporalTest, TestZoned2) { {"iso_year": 2009, "iso_week": 1, "iso_day_of_week": 1}, {"iso_year": 2011, "iso_week": 52, "iso_day_of_week": 7}, null])"); auto quarter = "[1, 1, 1, 2, 1, 4, 4, 4, 1, 1, 1, 1, 4, 4, 4, 1, null]"; - // Note: GCC behaves differently for Australia/Broken_Hill around the year 2000 zone - // rule transition. The expected hour for 2000-02-29 (index 1) differs because the - // offset is wrong (+9:30 instead of +10:30). - std::string hour = "[9, 9, 9, 13, 11, 12, 13, 14, 15, 17, 18, 19, 20, 10, 10, 11, null]"; -#if ARROW_USE_STD_CHRONO - hour.replace(hour.find("[9, 9, "), 6, "[9, 8, "); -#endif + auto hour = "[9, 9, 9, 13, 11, 12, 13, 14, 15, 17, 18, 19, 20, 10, 10, 11, null]"; auto minute = "[30, 53, 59, 3, 35, 40, 45, 50, 55, 0, 5, 10, 15, 30, 30, 32, null]"; CheckScalarUnary("year", unit, times_seconds_precision, int64(), year); @@ -897,7 +895,7 @@ TEST_F(ScalarTemporalTest, TestZoned2) { CheckScalarUnary("iso_calendar", ArrayFromJSON(unit, times_seconds_precision), iso_calendar); CheckScalarUnary("quarter", unit, times_seconds_precision, int64(), quarter); - CheckScalarUnary("hour", unit, times_seconds_precision, int64(), hour.c_str()); + CheckScalarUnary("hour", unit, times_seconds_precision, int64(), hour); CheckScalarUnary("minute", unit, times_seconds_precision, int64(), minute); CheckScalarUnary("second", unit, times_seconds_precision, int64(), second); CheckScalarUnary("millisecond", unit, times_seconds_precision, int64(), zeros); @@ -2824,32 +2822,26 @@ TEST_F(ScalarTemporalTestMultipleSinceGreaterUnit, CeilZoned) { "2020-01-01 01:09:00", "2019-12-31 02:22:00", "2019-12-30 03:22:00", "2009-12-31 04:22:00", "2010-01-01 05:35:00", "2010-01-03 06:43:00", "2010-01-04 07:43:00", "2006-01-01 08:43:00", "2005-12-31 09:56:00", "2008-12-28 00:09:00", "2008-12-29 00:09:00", "2012-01-01 01:09:00", null])"; - std::string ceil_15_hour = R"([ + const char* ceil_15_hour = R"([ "1970-01-01 05:30:00", "2000-03-01 04:30:00", "1899-01-01 06:00:00", "2033-05-18 05:30:00", "2020-01-01 04:30:00", "2019-12-31 04:30:00", "2019-12-30 04:30:00", "2009-12-31 04:30:00", "2010-01-01 19:30:00", "2010-01-03 19:30:00", "2010-01-04 19:30:00", "2006-01-01 19:30:00", "2005-12-31 19:30:00", "2008-12-28 04:30:00", "2008-12-29 04:30:00", "2012-01-01 04:30:00", null])"; - std::string ceil_15_day = R"([ + const char* ceil_15_day = R"([ "1970-01-15 14:30:00", "2000-03-15 13:30:00", "1899-01-15 15:00:00", "2033-05-30 14:30:00", "2020-01-15 13:30:00", "2020-01-14 13:30:00", "2019-12-30 13:30:00", "2010-01-14 13:30:00", "2010-01-15 13:30:00", "2010-01-15 13:30:00", "2010-01-15 13:30:00", "2006-01-15 13:30:00", "2006-01-14 13:30:00", "2008-12-30 13:30:00", "2008-12-30 13:30:00", "2012-01-15 13:30:00", null])"; - std::string ceil_3_weeks = R"([ + const char* ceil_3_weeks = R"([ "1970-01-18 14:30:00", "2000-03-05 13:30:00", "1899-01-22 15:00:00", "2033-05-29 14:30:00", "2020-01-19 13:30:00", "2020-01-19 13:30:00", "2020-01-19 13:30:00", "2010-01-24 13:30:00", "2010-01-24 13:30:00", "2010-01-24 13:30:00", "2010-01-24 13:30:00", "2006-01-22 13:30:00", "2006-01-22 13:30:00", "2009-01-11 13:30:00", "2009-01-18 13:30:00", "2012-01-22 13:30:00", null])"; - std::string ceil_3_weeks_sunday = R"([ + const char* ceil_3_weeks_sunday = R"([ "1970-01-24 14:30:00", "2000-03-25 13:30:00", "1899-01-21 15:00:00", "2033-05-28 14:30:00", "2020-01-18 13:30:00", "2020-01-18 13:30:00", "2020-01-18 13:30:00", "2010-01-23 13:30:00", "2010-01-23 13:30:00", "2010-01-23 13:30:00", "2010-01-23 13:30:00", "2006-01-21 13:30:00", "2006-01-21 13:30:00", "2009-01-24 13:30:00", "2009-01-24 13:30:00", "2012-01-21 13:30:00", null])"; -#if ARROW_USE_STD_CHRONO - ceil_15_hour.replace(ceil_15_hour.find("2000-03-01 04:30:00"), 19, "2000-03-01 05:30:00"); - ceil_15_day.replace(ceil_15_day.find("2000-03-15 13:30:00"), 19, "2000-03-15 14:30:00"); - ceil_3_weeks.replace(ceil_3_weeks.find("2000-03-05 13:30:00"), 19, "2000-03-05 14:30:00"); - ceil_3_weeks_sunday.replace(ceil_3_weeks_sunday.find("2000-03-25 13:30:00"), 19, "2000-03-25 14:30:00"); -#endif const char* ceil_5_months = R"([ "1970-05-31 14:30:00", "2000-05-31 14:30:00", "1899-05-31 14:30:00", "2033-05-31 14:30:00", "2020-05-31 14:30:00", "2020-03-31 13:30:00", "2020-03-31 13:30:00", "2010-03-31 13:30:00", @@ -2874,10 +2866,10 @@ TEST_F(ScalarTemporalTestMultipleSinceGreaterUnit, CeilZoned) { CheckScalarUnary(op, unit, times, unit, ceil_15_millisecond, &round_to_15_milliseconds); CheckScalarUnary(op, unit, times, unit, ceil_13_second, &round_to_13_seconds); CheckScalarUnary(op, unit, times, unit, ceil_13_minute, &round_to_13_minutes); - CheckScalarUnary(op, unit, times, unit, ceil_15_hour.c_str(), &round_to_15_hours); - CheckScalarUnary(op, unit, times, unit, ceil_15_day.c_str(), &round_to_15_days); - CheckScalarUnary(op, unit, times, unit, ceil_3_weeks.c_str(), &round_to_3_weeks); - CheckScalarUnary(op, unit, times, unit, ceil_3_weeks_sunday.c_str(), &round_to_3_weeks_sunday); + CheckScalarUnary(op, unit, times, unit, ceil_15_hour, &round_to_15_hours); + CheckScalarUnary(op, unit, times, unit, ceil_15_day, &round_to_15_days); + CheckScalarUnary(op, unit, times, unit, ceil_3_weeks, &round_to_3_weeks); + CheckScalarUnary(op, unit, times, unit, ceil_3_weeks_sunday, &round_to_3_weeks_sunday); CheckScalarUnary(op, unit, times, unit, ceil_5_months, &round_to_5_months); CheckScalarUnary(op, unit, times, unit, ceil_3_quarters, &round_to_3_quarters); CheckScalarUnary(op, unit, times, unit, ceil_15_years, &round_to_15_years); @@ -3220,32 +3212,26 @@ TEST_F(ScalarTemporalTestMultipleSinceGreaterUnit, FloorZoned) { "2020-01-01 00:56:00", "2019-12-31 02:09:00", "2019-12-30 03:09:00", "2009-12-31 04:09:00", "2010-01-01 05:22:00", "2010-01-03 06:30:00", "2010-01-04 07:30:00", "2006-01-01 08:30:00", "2005-12-31 09:43:00", "2008-12-27 23:56:00", "2008-12-28 23:56:00", "2012-01-01 00:56:00", null])"; - std::string floor_15_hour = R"([ + const char* floor_15_hour = R"([ "1969-12-31 14:30:00", "2000-02-29 13:30:00", "1898-12-31 15:00:00", "2033-05-17 14:30:00", "2019-12-31 13:30:00", "2019-12-30 13:30:00", "2019-12-29 13:30:00", "2009-12-30 13:30:00", "2010-01-01 04:30:00", "2010-01-03 04:30:00", "2010-01-04 04:30:00", "2006-01-01 04:30:00", "2005-12-31 04:30:00", "2008-12-27 13:30:00", "2008-12-28 13:30:00", "2011-12-31 13:30:00", null])"; - std::string floor_15_day = R"([ + const char* floor_15_day = R"([ "1969-12-31 14:30:00", "2000-02-29 13:30:00", "1898-12-31 15:00:00", "2033-05-15 14:30:00", "2019-12-31 13:30:00", "2019-12-30 13:30:00", "2019-12-15 13:30:00", "2009-12-30 13:30:00", "2009-12-31 13:30:00", "2009-12-31 13:30:00", "2009-12-31 13:30:00", "2005-12-31 13:30:00", "2005-12-30 13:30:00", "2008-12-15 13:30:00", "2008-12-15 13:30:00", "2011-12-31 13:30:00", null])"; - std::string floor_3_weeks = R"([ + const char* floor_3_weeks = R"([ "1969-12-28 14:30:00", "2000-02-13 13:30:00", "1899-01-01 15:00:00", "2033-05-08 14:30:00", "2019-12-29 13:30:00", "2019-12-29 13:30:00", "2019-12-29 13:30:00", "2010-01-03 13:30:00", "2010-01-03 13:30:00", "2010-01-03 13:30:00", "2010-01-03 13:30:00", "2006-01-01 13:30:00", "2006-01-01 13:30:00", "2008-12-21 13:30:00", "2008-12-28 13:30:00", "2012-01-01 13:30:00", null])"; - std::string floor_3_weeks_sunday = R"([ + const char* floor_3_weeks_sunday = R"([ "1970-01-03 14:30:00", "2000-03-04 13:30:00", "1898-12-31 15:00:00", "2033-05-07 14:30:00", "2019-12-28 13:30:00", "2019-12-28 13:30:00", "2019-12-28 13:30:00", "2010-01-02 13:30:00", "2010-01-02 13:30:00", "2010-01-02 13:30:00", "2010-01-02 13:30:00", "2005-12-31 13:30:00", "2005-12-31 13:30:00", "2009-01-03 13:30:00", "2009-01-03 13:30:00", "2011-12-31 13:30:00", null])"; -#if ARROW_USE_STD_CHRONO - floor_15_hour.replace(floor_15_hour.find("2000-02-29 13:30:00"), 19, "2000-02-29 14:30:00"); - floor_15_day.replace(floor_15_day.find("2000-02-29 13:30:00"), 19, "2000-02-29 14:30:00"); - floor_3_weeks.replace(floor_3_weeks.find("2000-02-13 13:30:00"), 19, "2000-02-13 14:30:00"); - floor_3_weeks_sunday.replace(floor_3_weeks_sunday.find("2000-03-04 13:30:00"), 19, "2000-03-04 14:30:00"); -#endif const char* floor_5_months = R"([ "1969-12-31 14:30:00", "1999-12-31 13:30:00", "1898-12-31 15:00:00", "2032-12-31 13:30:00", "2019-12-31 13:30:00", "2019-10-31 13:30:00", "2019-10-31 13:30:00", "2009-10-31 13:30:00", @@ -3272,10 +3258,10 @@ TEST_F(ScalarTemporalTestMultipleSinceGreaterUnit, FloorZoned) { &round_to_15_milliseconds); CheckScalarUnary(op, unit, times, unit, floor_13_second, &round_to_13_seconds); CheckScalarUnary(op, unit, times, unit, floor_13_minute, &round_to_13_minutes); - CheckScalarUnary(op, unit, times, unit, floor_15_hour.c_str(), &round_to_15_hours); - CheckScalarUnary(op, unit, times, unit, floor_15_day.c_str(), &round_to_15_days); - CheckScalarUnary(op, unit, times, unit, floor_3_weeks.c_str(), &round_to_3_weeks); - CheckScalarUnary(op, unit, times, unit, floor_3_weeks_sunday.c_str(), &round_to_3_weeks_sunday); + CheckScalarUnary(op, unit, times, unit, floor_15_hour, &round_to_15_hours); + CheckScalarUnary(op, unit, times, unit, floor_15_day, &round_to_15_days); + CheckScalarUnary(op, unit, times, unit, floor_3_weeks, &round_to_3_weeks); + CheckScalarUnary(op, unit, times, unit, floor_3_weeks_sunday, &round_to_3_weeks_sunday); CheckScalarUnary(op, unit, times, unit, floor_5_months, &round_to_5_months); CheckScalarUnary(op, unit, times, unit, floor_3_quarters, &round_to_3_quarters); CheckScalarUnary(op, unit, times, unit, floor_15_years, &round_to_15_years); @@ -3659,32 +3645,26 @@ TEST_F(ScalarTemporalTestMultipleSinceGreaterUnit, RoundZoned) { "2020-01-01 01:09:00", "2019-12-31 02:09:00", "2019-12-30 03:09:00", "2009-12-31 04:22:00", "2010-01-01 05:22:00", "2010-01-03 06:30:00", "2010-01-04 07:30:00", "2006-01-01 08:43:00", "2005-12-31 09:43:00", "2008-12-27 23:56:00", "2008-12-28 23:56:00", "2012-01-01 00:56:00", null])"; - std::string round_15_hour = R"([ + const char* round_15_hour = R"([ "1970-01-01 05:30:00", "2000-03-01 04:30:00", "1899-01-01 06:00:00", "2033-05-18 05:30:00", "2020-01-01 04:30:00", "2019-12-31 04:30:00", "2019-12-30 04:30:00", "2009-12-31 04:30:00", "2010-01-01 04:30:00", "2010-01-03 04:30:00", "2010-01-04 04:30:00", "2006-01-01 04:30:00", "2005-12-31 04:30:00", "2008-12-28 04:30:00", "2008-12-29 04:30:00", "2012-01-01 04:30:00", null])"; - std::string round_15_day = R"([ + const char* round_15_day = R"([ "1969-12-31 14:30:00", "2000-02-29 13:30:00", "1898-12-31 15:00:00", "2033-05-15 14:30:00", "2019-12-31 13:30:00", "2019-12-30 13:30:00", "2019-12-30 13:30:00", "2009-12-30 13:30:00", "2009-12-31 13:30:00", "2009-12-31 13:30:00", "2009-12-31 13:30:00", "2005-12-31 13:30:00", "2005-12-30 13:30:00", "2008-12-30 13:30:00", "2008-12-30 13:30:00", "2011-12-31 13:30:00", null])"; - std::string round_3_weeks = R"([ + const char* round_3_weeks = R"([ "1969-12-28 14:30:00", "2000-03-05 13:30:00", "1899-01-01 15:00:00", "2033-05-08 14:30:00", "2019-12-29 13:30:00", "2019-12-29 13:30:00", "2019-12-29 13:30:00", "2010-01-03 13:30:00", "2010-01-03 13:30:00", "2010-01-03 13:30:00", "2010-01-03 13:30:00", "2006-01-01 13:30:00", "2006-01-01 13:30:00", "2008-12-21 13:30:00", "2008-12-28 13:30:00", "2012-01-01 13:30:00",null])"; - std::string round_3_weeks_sunday = R"([ + const char* round_3_weeks_sunday = R"([ "1970-01-03 14:30:00", "2000-03-04 13:30:00", "1898-12-31 15:00:00", "2033-05-28 14:30:00", "2019-12-28 13:30:00", "2019-12-28 13:30:00", "2019-12-28 13:30:00", "2010-01-02 13:30:00", "2010-01-02 13:30:00", "2010-01-02 13:30:00", "2010-01-02 13:30:00", "2005-12-31 13:30:00", "2005-12-31 13:30:00", "2009-01-03 13:30:00", "2009-01-03 13:30:00", "2011-12-31 13:30:00", null])"; -#if ARROW_USE_STD_CHRONO - round_15_hour.replace(round_15_hour.find("2000-03-01 04:30:00"), 19, "2000-03-01 05:30:00"); - round_15_day.replace(round_15_day.find("2000-02-29 13:30:00"), 19, "2000-02-29 14:30:00"); - round_3_weeks.replace(round_3_weeks.find("2000-03-05 13:30:00"), 19, "2000-03-05 14:30:00"); - round_3_weeks_sunday.replace(round_3_weeks_sunday.find("2000-03-04 13:30:00"), 19, "2000-03-04 14:30:00"); -#endif const char* round_5_months = R"([ "1969-12-31 14:30:00", "1999-12-31 13:30:00", "1898-12-31 15:00:00", "2033-05-31 14:30:00", "2019-12-31 13:30:00", "2019-10-31 13:30:00", "2019-10-31 13:30:00", "2009-10-31 13:30:00", @@ -3711,10 +3691,10 @@ TEST_F(ScalarTemporalTestMultipleSinceGreaterUnit, RoundZoned) { &round_to_15_milliseconds); CheckScalarUnary(op, unit, times, unit, round_13_second, &round_to_13_seconds); CheckScalarUnary(op, unit, times, unit, round_13_minute, &round_to_13_minutes); - CheckScalarUnary(op, unit, times, unit, round_15_hour.c_str(), &round_to_15_hours); - CheckScalarUnary(op, unit, times, unit, round_15_day.c_str(), &round_to_15_days); - CheckScalarUnary(op, unit, times, unit, round_3_weeks.c_str(), &round_to_3_weeks); - CheckScalarUnary(op, unit, times, unit, round_3_weeks_sunday.c_str(), &round_to_3_weeks_sunday); + CheckScalarUnary(op, unit, times, unit, round_15_hour, &round_to_15_hours); + CheckScalarUnary(op, unit, times, unit, round_15_day, &round_to_15_days); + CheckScalarUnary(op, unit, times, unit, round_3_weeks, &round_to_3_weeks); + CheckScalarUnary(op, unit, times, unit, round_3_weeks_sunday, &round_to_3_weeks_sunday); CheckScalarUnary(op, unit, times, unit, round_5_months, &round_to_5_months); CheckScalarUnary(op, unit, times, unit, round_3_quarters, &round_to_3_quarters); CheckScalarUnary(op, unit, times, unit, round_15_years, &round_to_15_years); diff --git a/cpp/src/arrow/util/chrono_internal.h b/cpp/src/arrow/util/chrono_internal.h index 2eef96f8adb..67c5818b210 100644 --- a/cpp/src/arrow/util/chrono_internal.h +++ b/cpp/src/arrow/util/chrono_internal.h @@ -21,9 +21,9 @@ /// \brief Abstraction layer for C++20 chrono calendar/timezone APIs /// /// This header provides a unified interface for chrono calendar and timezone -/// functionality. On compilers with full C++20 chrono support (MSVC 16.10+ and -/// GCC 14+), it uses std::chrono. On other compilers, it falls back to the -/// vendored Howard Hinnant date library. +/// functionality. On compilers with full C++20 chrono support, it uses +/// std::chrono. On other compilers, it falls back to the vendored Howard Hinnant +/// date library. /// /// The main benefit is on Windows where std::chrono uses the system timezone /// database, eliminating the need for users to install IANA tzdata separately. @@ -37,26 +37,17 @@ // Feature detection for C++20 chrono timezone support // We only enable for compilers with FULL support (not partial) +// https://en.cppreference.com/w/cpp/compiler_support/20.html#cpp_lib_chrono_201907L // -// Compiler support -// (https://en.cppreference.com/w/cpp/compiler_support/20.html#cpp_lib_chrono_201907L): -// - MSVC 19.29 (VS 2019 16.10)+: Full support, uses Windows TZ database -// - GCC 14+: Full support, requires tzdata.zi on system -// - GCC 11-13: Partial support only -// - Clang/libc++: Still partial even in version 19 -// - Apple Clang: Still partial - -#if defined(__cpp_lib_chrono) && __cpp_lib_chrono >= 201907L -# if defined(_MSC_VER) -// MSVC 19.29+: Full support, uses Windows internal TZ database -# define ARROW_USE_STD_CHRONO 1 -# elif defined(__GLIBCXX__) && __GNUC__ >= 14 -// GCC 14+ with libstdc++: Full support, requires tzdata.zi -# define ARROW_USE_STD_CHRONO 1 -# endif -#endif - -#ifndef ARROW_USE_STD_CHRONO +// MSVC 19.29+ (VS16.10+): Full C++20 chrono support, uses Windows internal TZ database. +// GCC libstdc++ has a bug where DST state is incorrectly reset when a timezone +// transitions between rule sets in tzdata.zi (e.g., Australia/Broken_Hill around +// 2000-02-29 23:23:24). +// Until this is fixed, we use the vendored date.h library for GCC. + +#if defined(_MSC_VER) && defined(__cpp_lib_chrono) && __cpp_lib_chrono >= 201907L +# define ARROW_USE_STD_CHRONO 1 +#else # define ARROW_USE_STD_CHRONO 0 #endif From d82f99029ff763314dcdfe5c4dc1f04a1032ac1b Mon Sep 17 00:00:00 2001 From: Rok Mihevc Date: Tue, 23 Dec 2025 18:59:58 +0100 Subject: [PATCH 4/4] simplify with C++20 chrono features --- .../compute/kernels/scalar_temporal_test.cc | 2 +- cpp/src/arrow/util/chrono_internal.h | 147 +++--------------- 2 files changed, 21 insertions(+), 128 deletions(-) diff --git a/cpp/src/arrow/compute/kernels/scalar_temporal_test.cc b/cpp/src/arrow/compute/kernels/scalar_temporal_test.cc index da1172212a2..ebaa7aecdc4 100644 --- a/cpp/src/arrow/compute/kernels/scalar_temporal_test.cc +++ b/cpp/src/arrow/compute/kernels/scalar_temporal_test.cc @@ -20,7 +20,6 @@ #include #include "arrow/compute/api_scalar.h" -#include "arrow/util/chrono_internal.h" // for ARROW_USE_STD_CHRONO #include "arrow/compute/cast.h" #include "arrow/compute/kernels/test_util_internal.h" #include "arrow/testing/gtest_util.h" @@ -30,6 +29,7 @@ #include "arrow/type_fwd.h" #include "arrow/type_traits.h" #include "arrow/util/checked_cast.h" +#include "arrow/util/chrono_internal.h" // for ARROW_USE_STD_CHRONO #include "arrow/util/formatting.h" #include "arrow/util/logging_internal.h" diff --git a/cpp/src/arrow/util/chrono_internal.h b/cpp/src/arrow/util/chrono_internal.h index 67c5818b210..453d952d1dc 100644 --- a/cpp/src/arrow/util/chrono_internal.h +++ b/cpp/src/arrow/util/chrono_internal.h @@ -29,9 +29,6 @@ /// database, eliminating the need for users to install IANA tzdata separately. #include -#include -#include -#include #include #include @@ -53,6 +50,9 @@ #if ARROW_USE_STD_CHRONO // Use C++20 standard library chrono +# include +# include +# include #else // Use vendored Howard Hinnant date library # include "arrow/vendored/datetime.h" @@ -111,26 +111,24 @@ using nonexistent_local_time = std::chrono::nonexistent_local_time; using ambiguous_local_time = std::chrono::ambiguous_local_time; // Weekday constants -inline constexpr std::chrono::weekday Monday{1}; -inline constexpr std::chrono::weekday Sunday{0}; +using std::chrono::Monday; +using std::chrono::Sunday; // Rounding functions using std::chrono::ceil; using std::chrono::floor; using std::chrono::round; -// trunc is not in std::chrono - implement proper truncation toward zero -// floor rounds toward negative infinity, but trunc rounds toward zero +// trunc (truncation toward zero) is not in std::chrono, only floor/ceil/round template constexpr ToDuration trunc(const std::chrono::duration& d) { - auto floor_result = std::chrono::floor(d); - auto remainder = d - floor_result; - // If original was negative and there's a non-zero remainder, - // floor went too far negative, so add one unit back - if (d.count() < 0 && remainder.count() != 0) { - return floor_result + ToDuration{1}; + auto floored = std::chrono::floor(d); + // floor rounds toward -infinity; for negative values with remainder, add 1 to get + // toward zero + if (d.count() < 0 && (d - floored).count() != 0) { + return floored + ToDuration{1}; } - return floor_result; + return floored; } // Timezone lookup @@ -140,127 +138,22 @@ inline const time_zone* locate_zone(std::string_view tz_name) { inline const time_zone* current_zone() { return std::chrono::current_zone(); } -// Helper to get subsecond decimal places based on duration period -template -constexpr int get_subsecond_decimals() { - using Period = typename Duration::period; - if constexpr (Period::den == 1000) - return 3; // milliseconds - else if constexpr (Period::den == 1000000) - return 6; // microseconds - else if constexpr (Period::den == 1000000000) - return 9; // nanoseconds - else - return 0; // seconds or coarser -} - -// Formatting support with subsecond precision and timezone handling -// Mimics the vendored date library's to_stream behavior for compatibility +// Formatting support - streams directly using C++20 std::vformat_to +// Provides: direct streaming, stream state preservation, chaining, rich format specifiers template std::basic_ostream& to_stream( std::basic_ostream& os, const CharT* fmt, const std::chrono::zoned_time& zt) { - // Get local time and timezone info - auto lt = zt.get_local_time(); - auto info = zt.get_info(); - - auto lt_days = std::chrono::floor(lt); - auto ymd = year_month_day{lt_days}; - - // Calculate time of day components - auto time_since_midnight = lt - local_time{lt_days}; - auto total_secs = std::chrono::duration_cast(time_since_midnight); - auto h = std::chrono::duration_cast(time_since_midnight); - auto m = std::chrono::duration_cast(time_since_midnight - h); - auto s = std::chrono::duration_cast(time_since_midnight - h - m); - - // Build std::tm for strftime - std::tm tm{}; - tm.tm_sec = static_cast(s.count()); - tm.tm_min = static_cast(m.count()); - tm.tm_hour = static_cast(h.count()); - tm.tm_mday = static_cast(static_cast(ymd.day())); - tm.tm_mon = static_cast(static_cast(ymd.month())) - 1; - tm.tm_year = static_cast(ymd.year()) - 1900; - - auto wd = weekday{lt_days}; - tm.tm_wday = static_cast(wd.c_encoding()); - - auto year_start = - std::chrono::local_days{ymd.year() / std::chrono::January / std::chrono::day{1}}; - tm.tm_yday = static_cast((lt_days - year_start).count()); - tm.tm_isdst = info.save != std::chrono::minutes{0} ? 1 : 0; - - // Timezone offset calculation - auto offset_mins = std::chrono::duration_cast(info.offset); - bool neg_offset = offset_mins.count() < 0; - auto abs_offset = neg_offset ? -offset_mins : offset_mins; - auto off_h = std::chrono::duration_cast(abs_offset); - auto off_m = abs_offset - off_h; - - // Calculate subsecond value - constexpr int decimals = get_subsecond_decimals(); - int64_t subsec_value = 0; - if constexpr (decimals > 0) { - auto subsec_duration = time_since_midnight - total_secs; - subsec_value = std::chrono::duration_cast(subsec_duration).count(); - if (subsec_value < 0) subsec_value = -subsec_value; - } - - // Parse format string, handle %S, %z, %Z specially - std::string result; - for (const CharT* p = fmt; *p; ++p) { - if (*p == '%' && *(p + 1)) { - CharT spec = *(p + 1); - if (spec == 'S') { - // %S with subsecond precision - result += (tm.tm_sec < 10 ? "0" : "") + std::to_string(tm.tm_sec); - if constexpr (decimals > 0) { - std::ostringstream ss; - ss << '.' << std::setfill('0') << std::setw(decimals) << subsec_value; - result += ss.str(); - } - ++p; - } else if (spec == 'z') { - // %z timezone offset - std::ostringstream ss; - ss << (neg_offset ? '-' : '+') << std::setfill('0') << std::setw(2) - << off_h.count() << std::setfill('0') << std::setw(2) << off_m.count(); - result += ss.str(); - ++p; - } else if (spec == 'Z') { - // %Z timezone abbreviation - result += info.abbrev; - ++p; - } else { - // Use strftime for other specifiers - char buf[64]; - char small_fmt[3] = {'%', static_cast(spec), '\0'}; - if (std::strftime(buf, sizeof(buf), small_fmt, &tm) > 0) { - result += buf; - } - ++p; - } - } else { - result += static_cast(*p); - } - } - - return os << result; + std::vformat_to(std::ostreambuf_iterator(os), std::string("{:") + fmt + "}", + std::make_format_args(zt)); + return os; } +// Format a duration using strftime-like format specifiers +// Converts "%H%M" style to C++20's "{:%H%M}" style and uses std::vformat template std::string format(const char* fmt, const Duration& d) { - std::ostringstream ss; - auto total_minutes = std::chrono::duration_cast(d).count(); - bool negative = total_minutes < 0; - if (negative) total_minutes = -total_minutes; - auto hours = total_minutes / 60; - auto mins = total_minutes % 60; - ss << (negative ? "-" : "+"); - ss << std::setfill('0') << std::setw(2) << hours; - ss << std::setfill('0') << std::setw(2) << mins; - return ss.str(); + return std::vformat(std::string("{:") + fmt + "}", std::make_format_args(d)); } // Literals namespace