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_test.cc b/cpp/src/arrow/compute/kernels/scalar_temporal_test.cc index 3350fb805c4..ebaa7aecdc4 100644 --- a/cpp/src/arrow/compute/kernels/scalar_temporal_test.cc +++ b/cpp/src/arrow/compute/kernels/scalar_temporal_test.cc @@ -29,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" @@ -38,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 = 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..453d952d1dc --- /dev/null +++ b/cpp/src/arrow/util/chrono_internal.h @@ -0,0 +1,269 @@ +// 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, 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 + +// 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 +// +// 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 + +#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" +#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 +using std::chrono::Monday; +using std::chrono::Sunday; + +// Rounding functions +using std::chrono::ceil; +using std::chrono::floor; +using std::chrono::round; + +// trunc (truncation toward zero) is not in std::chrono, only floor/ceil/round +template +constexpr ToDuration trunc(const std::chrono::duration& d) { + 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 floored; +} + +// 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(); } + +// 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) { + 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) { + return std::vformat(std::string("{:") + fmt + "}", std::make_format_args(d)); +} + +// 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