Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cpp/src/arrow/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,8 @@ set(ARROW_VENDORED_SRCS
vendored/uriparser/UriResolve.c
vendored/uriparser/UriShorten.c)
if(APPLE)
list(APPEND ARROW_VENDORED_SRCS vendored/datetime/ios.mm)
# This wrapper excludes the iOS timezone implementation from standard-backend builds.
list(APPEND ARROW_VENDORED_SRCS vendored/datetime_ios.mm)
endif()
set_source_files_properties(vendored/datetime.cpp PROPERTIES SKIP_UNITY_BUILD_INCLUSION
ON)
Expand Down
30 changes: 15 additions & 15 deletions cpp/src/arrow/array/diff.cc
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,19 @@
#include "arrow/type_traits.h"
#include "arrow/util/bit_util.h"
#include "arrow/util/checked_cast.h"
#include "arrow/util/chrono_internal.h"
#include "arrow/util/float16.h"
#include "arrow/util/logging_internal.h"
#include "arrow/util/range.h"
#include "arrow/util/ree_util.h"
#include "arrow/util/string.h"
#include "arrow/util/unreachable.h"
#include "arrow/vendored/datetime.h"
#include "arrow/visit_type_inline.h"

namespace arrow {

namespace chrono = internal::chrono;

using internal::checked_cast;
using internal::checked_pointer_cast;
using internal::MakeLazyRange;
Expand Down Expand Up @@ -631,14 +633,13 @@ class MakeFormatterImpl {
template <typename T>
enable_if_date<T, Status> Visit(const T&) {
using unit = typename std::conditional<std::is_same<T, Date32Type>::value,
arrow_vendored::date::days,
std::chrono::milliseconds>::type;
chrono::days, std::chrono::milliseconds>::type;

static arrow_vendored::date::sys_days epoch{arrow_vendored::date::jan / 1 / 1970};
static chrono::sys_days epoch{chrono::jan / 1 / 1970};

impl_ = [](const Array& array, int64_t index, std::ostream* os) {
unit value(checked_cast<const NumericArray<T>&>(array).Value(index));
*os << arrow_vendored::date::format("%F", value + epoch);
*os << chrono::format("%F", value + epoch);
};
return Status::OK();
}
Expand Down Expand Up @@ -854,42 +855,41 @@ class MakeFormatterImpl {
auto value = checked_cast<const NumericArray<T>&>(array).Value(index);
// Using unqualified `format` directly would produce ambiguous
// lookup because of `std::format` (ARROW-15520).
namespace avd = arrow_vendored::date;
using std::chrono::nanoseconds;
using std::chrono::microseconds;
using std::chrono::milliseconds;
using std::chrono::seconds;
if (AddEpoch) {
static avd::sys_days epoch{avd::jan / 1 / 1970};
static chrono::sys_days epoch{chrono::jan / 1 / 1970};

switch (unit) {
case TimeUnit::NANO:
*os << avd::format(fmt, static_cast<nanoseconds>(value) + epoch);
*os << chrono::format(fmt, static_cast<nanoseconds>(value) + epoch);
break;
case TimeUnit::MICRO:
*os << avd::format(fmt, static_cast<microseconds>(value) + epoch);
*os << chrono::format(fmt, static_cast<microseconds>(value) + epoch);
break;
case TimeUnit::MILLI:
*os << avd::format(fmt, static_cast<milliseconds>(value) + epoch);
*os << chrono::format(fmt, static_cast<milliseconds>(value) + epoch);
break;
case TimeUnit::SECOND:
*os << avd::format(fmt, static_cast<seconds>(value) + epoch);
*os << chrono::format(fmt, static_cast<seconds>(value) + epoch);
break;
}
return;
}
switch (unit) {
case TimeUnit::NANO:
*os << avd::format(fmt, static_cast<nanoseconds>(value));
*os << chrono::format(fmt, static_cast<nanoseconds>(value));
break;
case TimeUnit::MICRO:
*os << avd::format(fmt, static_cast<microseconds>(value));
*os << chrono::format(fmt, static_cast<microseconds>(value));
break;
case TimeUnit::MILLI:
*os << avd::format(fmt, static_cast<milliseconds>(value));
*os << chrono::format(fmt, static_cast<milliseconds>(value));
break;
case TimeUnit::SECOND:
*os << avd::format(fmt, static_cast<seconds>(value));
*os << chrono::format(fmt, static_cast<seconds>(value));
break;
}
};
Expand Down
3 changes: 1 addition & 2 deletions cpp/src/arrow/compute/kernels/scalar_cast_temporal.cc
Original file line number Diff line number Diff line change
Expand Up @@ -462,8 +462,7 @@ struct ParseDate {
using value_type = typename DateType::c_type;

using duration_type =
typename std::conditional<std::is_same<DateType, Date32Type>::value,
arrow_vendored::date::days,
typename std::conditional<std::is_same<DateType, Date32Type>::value, chrono::days,
std::chrono::milliseconds>::type;

template <typename OutValue, typename Arg0Value>
Expand Down
22 changes: 22 additions & 0 deletions cpp/src/arrow/compute/kernels/scalar_temporal_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2003,6 +2003,28 @@ TEST_F(ScalarTemporalTest, TestAssumeTimezoneNonexistent) {
&options_earliest);
}

TEST_F(ScalarTemporalTest, StrftimeFormatSyntax) {
const auto type = timestamp(TimeUnit::MILLI, "UTC");
const char* input = R"(["1970-01-01T00:00:00.123", null])";
for (const auto& [format, expected] :
{std::pair{"", R"(["", null])"},
std::pair{"literal {%Y}", R"(["literal {1970}", null])"},
std::pair{"unmatched }%Y{", R"(["unmatched }1970{", null])"},
std::pair{"%Y}", R"(["1970}", null])"},
std::pair{"%Q %q %J %z %Z", R"(["123 ms %J +0000 UTC", null])"},
std::pair{"%% %n%t %Ez %Oz %OV %EJ end%",
R"(["% \n\t +00:00 +00:00 01 %EJ end%", null])"}}) {
SCOPED_TRACE(format);
const auto options = StrftimeOptions(format);
CheckScalarUnary("strftime", type, input, utf8(), expected, &options);
}

const auto options = StrftimeOptions("%Q %q");
CheckScalarUnary("strftime", timestamp(TimeUnit::MICRO, "UTC"),
R"(["1970-01-01T00:00:00.000001", null])", utf8(),
R"(["1 \u00b5s", null])", &options);
}

TEST_F(ScalarTemporalTest, StrftimeOffsetTimezone) {
auto options_ymdhms = StrftimeOptions("%Y-%m-%dT%H:%M:%S");

Expand Down
15 changes: 8 additions & 7 deletions cpp/src/arrow/config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@

#include <cstdint>

#include "arrow/util/chrono_internal.h"
#include "arrow/util/config.h"
#include "arrow/util/config_internal.h"
#include "arrow/util/cpu_info.h"
#include "arrow/vendored/datetime.h"

namespace arrow {

using internal::CpuInfo;
namespace chrono = internal::chrono;

namespace {

Expand Down Expand Up @@ -77,8 +78,8 @@ RuntimeInfo GetRuntimeInfo() {
MakeSimdLevelString([&](int64_t flags) { return cpu_info->IsSupported(flags); });
info.detected_simd_level =
MakeSimdLevelString([&](int64_t flags) { return cpu_info->IsDetected(flags); });
info.using_os_timezone_db = USE_OS_TZDB;
#if !USE_OS_TZDB
info.using_os_timezone_db = ARROW_CHRONO_USE_OS_TZDB;
#if !ARROW_CHRONO_USE_OS_TZDB
info.timezone_db_path = timezone_db_path;
#else
info.timezone_db_path = std::optional<std::string>();
Expand All @@ -91,10 +92,10 @@ RuntimeInfo GetRuntimeInfo() {
Status Initialize(const GlobalOptions& options) noexcept {
ARROW_SUPPRESS_DEPRECATION_WARNING
if (options.timezone_db_path.has_value()) {
#if !USE_OS_TZDB
#if !ARROW_CHRONO_USE_OS_TZDB
try {
arrow_vendored::date::set_install(options.timezone_db_path.value());
arrow_vendored::date::reload_tzdb();
chrono::set_install(options.timezone_db_path.value());
chrono::reload_tzdb();
} catch (const std::runtime_error& e) {
return Status::IOError(e.what());
}
Expand All @@ -103,7 +104,7 @@ Status Initialize(const GlobalOptions& options) noexcept {
return Status::Invalid(
"Arrow was set to use OS timezone database at compile time, "
"so a downloaded database cannot be provided at runtime.");
#endif // !USE_OS_TZDB
#endif // !ARROW_CHRONO_USE_OS_TZDB
}
ARROW_UNSUPPRESS_DEPRECATION_WARNING
return Status::OK();
Expand Down
4 changes: 2 additions & 2 deletions cpp/src/arrow/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ struct RuntimeInfo {
/// The SIMD level available on the OS and CPU
std::string detected_simd_level;

/// Whether using the OS-based timezone database
/// This is set at compile-time.
/// Whether the timezone database is managed by the OS or standard library,
/// rather than Arrow's configurable text database. This is set at compile-time.
bool using_os_timezone_db;

/// The path to the timezone database; by default None.
Expand Down
1 change: 0 additions & 1 deletion cpp/src/arrow/pretty_print.cc
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
#include "arrow/util/int_util_overflow.h"
#include "arrow/util/key_value_metadata.h"
#include "arrow/util/string.h"
#include "arrow/vendored/datetime.h"
#include "arrow/visit_array_inline.h"

namespace arrow {
Expand Down
14 changes: 14 additions & 0 deletions cpp/src/arrow/public_api_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,19 @@ TEST(Misc, BuildInfo) {
// TODO(GH-48593): Remove when libc++ supports std::chrono timezones.
ARROW_SUPPRESS_DEPRECATION_WARNING
TEST(Misc, SetTimezoneConfig) {
ASSERT_OK(Initialize(GlobalOptions{}));
if (GetRuntimeInfo().using_os_timezone_db) {
ASSERT_FALSE(GetRuntimeInfo().timezone_db_path.has_value());
// Standard-library backends must reject even an existing path rather than
// silently configuring an unused vendored database.
GlobalOptions options;
options.timezone_db_path = ".";
ASSERT_RAISES(Invalid, Initialize(options));
ASSERT_FALSE(GetRuntimeInfo().timezone_db_path.has_value());
EnvVarGuard tzdata("ARROW_TIMEZONE_DATABASE", ".");
ASSERT_OK(InitTestTimezoneDatabase());
return;
}
#ifndef _WIN32
GTEST_SKIP() << "Can only set the Timezone database on Windows";
#elif !defined(ARROW_FILESYSTEM)
Expand Down Expand Up @@ -164,6 +177,7 @@ TEST(Misc, SetTimezoneConfig) {

// Validate that tzdb is working
ASSERT_OK(arrow::Initialize(options));
ASSERT_EQ(GetRuntimeInfo().timezone_db_path, options.timezone_db_path);
#endif
}
ARROW_UNSUPPRESS_DEPRECATION_WARNING
Expand Down
2 changes: 2 additions & 0 deletions cpp/src/arrow/testing/util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ std::optional<std::string> GetTestTimezoneDatabaseRoot() {
// TODO(GH-48593): Remove when libc++ supports std::chrono timezones.
ARROW_SUPPRESS_DEPRECATION_WARNING
Status InitTestTimezoneDatabase() {
if (GetRuntimeInfo().using_os_timezone_db) return Status::OK();

auto maybe_tzdata = GetTestTimezoneDatabaseRoot();
// If missing, timezone database will default to %USERPROFILE%\Downloads\tzdata
if (!maybe_tzdata.has_value()) return Status::OK();
Expand Down
5 changes: 5 additions & 0 deletions cpp/src/arrow/util/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
# Headers: top level
arrow_install_all_headers("arrow/util")

# The automatic rule excludes internal headers, but these are dependencies of
# the installed formatting.h and value_parsing.h headers.
install(FILES chrono_config_internal.h chrono_internal.h
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/arrow/util")

#
# arrow_test_main
#
Expand Down
56 changes: 56 additions & 0 deletions cpp/src/arrow/util/chrono_config_internal.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// 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

#include <chrono>

// Share backend selection with the vendored implementation without including its
// headers. datetime.h undefines macros needed when compiling the implementation.
//
// On Windows, MSVC's standard library uses the system timezone database, while
// libstdc++ reads tzdata files (using TZDIR). Libraries without the C++20 timezone
// APIs, including older libc++, still require the vendored date library.
//
// Use the standard backend by default. Builds may explicitly define
// ARROW_USE_STD_CHRONO to 0 or 1 when they need to select a backend.
//
// Automatically disable the default for libraries without the C++20 timezone APIs.
// On non-Windows, older libstdc++ versions also need the fallback because of
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=116110 (fully fixed in GCC 16.2).
// Check library macros, not __GNUC__, so Clang using libstdc++ agrees with GCC.
// The datestamp distinguishes 16.2 (2026-08-07) from 16.1 and early snapshots.
// Keep the existing Windows backend selection unchanged.
#ifndef ARROW_USE_STD_CHRONO
# define ARROW_USE_STD_CHRONO 1
# if !defined(__cpp_lib_chrono) || __cpp_lib_chrono < 201907L
# undef ARROW_USE_STD_CHRONO
# define ARROW_USE_STD_CHRONO 0
# elif !defined(_WIN32) && defined(__GLIBCXX__) && \
(!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE < 16 || __GLIBCXX__ < 20260807)
# undef ARROW_USE_STD_CHRONO
# define ARROW_USE_STD_CHRONO 0
# endif
#endif

// Only the vendored Windows text database supports setting its path via Arrow.
// The non-Windows vendored backend uses USE_OS_TZDB (see datetime/visibility.h).
#if ARROW_USE_STD_CHRONO || !defined(_WIN32)
# define ARROW_CHRONO_USE_OS_TZDB 1
#else
# define ARROW_CHRONO_USE_OS_TZDB 0
#endif
Loading
Loading