From 172c90f423700fbb6b7863b61c9684d2ae775ba1 Mon Sep 17 00:00:00 2001 From: Pearu Peterson Date: Mon, 19 Oct 2026 20:32:34 +0300 Subject: [PATCH 1/3] GH-54641: [C++] Support locale directives in the vendored strptime on Windows The vendored musl strptime, used on Windows, handles %a, %A, %b, %B, %h, %c, %p, %r, %x and %X only when nl_langinfo() is available, which it is not on Windows. Provide a C-locale replacement for the items used. ARROW_TEST_FALLBACK_LANGINFO compiles the replacement on other platforms. Drafted by Claude Code (an AI agent) and reviewed & approved by pearu. --- cpp/src/arrow/util/value_parsing_test.cc | 32 ++++++++++++++++++++++++ cpp/src/arrow/vendored/musl/strptime.c | 19 +++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/cpp/src/arrow/util/value_parsing_test.cc b/cpp/src/arrow/util/value_parsing_test.cc index b61f777685b7..6eb387fc0319 100644 --- a/cpp/src/arrow/util/value_parsing_test.cc +++ b/cpp/src/arrow/util/value_parsing_test.cc @@ -876,6 +876,38 @@ TEST(TimestampParser, StrptimeParser) { } } +TEST(TimestampParser, StrptimeLocaleDirectives) { + // Directives that use the names and formats of the C locale + struct Case { + std::string format; + std::string value; + std::string iso8601; + }; + + std::vector cases = { + {"%a %d %b %Y", "Sun 15 Feb 2015", "2015-02-15 00:00:00"}, + {"%A %d %B %Y", "Sunday 15 February 2015", "2015-02-15 00:00:00"}, + {"%d %h %Y", "15 Feb 2015", "2015-02-15 00:00:00"}, + {"%d-%b-%Y", "15-feb-2015", "2015-02-15 00:00:00"}, + {"%c", "Sun Feb 15 14:30:00 2015", "2015-02-15 14:30:00"}, + {"%m/%d/%Y %I:%M %p", "02/15/2015 02:30 PM", "2015-02-15 14:30:00"}, + {"%Y-%m-%d %r", "2015-02-15 02:30:00 PM", "2015-02-15 14:30:00"}, + {"%x", "02/15/15", "2015-02-15 00:00:00"}, + {"%Y-%m-%d %X", "2015-02-15 14:30:00", "2015-02-15 14:30:00"}, + }; + + for (const auto& case_ : cases) { + SCOPED_TRACE(case_.format); + auto parser = TimestampParser::MakeStrptime(case_.format); + int64_t converted, expected; + ASSERT_TRUE( + (*parser)(case_.value.c_str(), case_.value.size(), TimeUnit::SECOND, &converted)); + ASSERT_TRUE(ParseTimestampISO8601(case_.iso8601.c_str(), case_.iso8601.size(), + TimeUnit::SECOND, &expected)); + ASSERT_EQ(expected, converted); + } +} + TEST(TimestampParser, StrptimeZoneOffset) { if (!kStrptimeSupportsZone) { GTEST_SKIP() << "strptime does not support %z on this platform"; diff --git a/cpp/src/arrow/vendored/musl/strptime.c b/cpp/src/arrow/vendored/musl/strptime.c index 41912fd1bbf4..7982dde2dc54 100644 --- a/cpp/src/arrow/vendored/musl/strptime.c +++ b/cpp/src/arrow/vendored/musl/strptime.c @@ -17,12 +17,29 @@ #undef HAVE_LANGINFO -#ifndef _WIN32 +#if !defined(_WIN32) && !defined(ARROW_TEST_FALLBACK_LANGINFO) #define HAVE_LANGINFO 1 #endif #ifdef HAVE_LANGINFO #include +#else +// C-locale replacement for nl_langinfo(), restricted to the items used below. +// The constants follow the glibc/musl layout that the symbolic_range loop +// relies on: full names directly follow the abbreviated ones. +enum { ABDAY_1 = 0, DAY_1 = 7, ABMON_1 = 14, MON_1 = 26, AM_STR = 38, PM_STR = 39, + D_T_FMT = 40, D_FMT = 41, T_FMT = 42, T_FMT_AMPM = 43 }; +static const char *arrow_c_langinfo[] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", + "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December", + "AM", "PM", + "%a %b %e %T %Y", "%m/%d/%y", "%H:%M:%S", "%I:%M:%S %p"}; +#define nl_langinfo(x) arrow_c_langinfo[x] +#define HAVE_LANGINFO 1 #endif #define strptime arrow_strptime From 1b6dfb1880e41de8b765484465f62469603be380 Mon Sep 17 00:00:00 2001 From: Pearu Peterson Date: Tue, 20 Oct 2026 10:29:27 +0300 Subject: [PATCH 2/3] Remove the fallback test switch and cite the source of the C-locale values Drafted by Claude Code (an AI agent) and reviewed & approved by pearu. --- cpp/src/arrow/vendored/musl/strptime.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cpp/src/arrow/vendored/musl/strptime.c b/cpp/src/arrow/vendored/musl/strptime.c index 7982dde2dc54..eb325108b8a8 100644 --- a/cpp/src/arrow/vendored/musl/strptime.c +++ b/cpp/src/arrow/vendored/musl/strptime.c @@ -17,7 +17,7 @@ #undef HAVE_LANGINFO -#if !defined(_WIN32) && !defined(ARROW_TEST_FALLBACK_LANGINFO) +#ifndef _WIN32 #define HAVE_LANGINFO 1 #endif @@ -25,8 +25,10 @@ #include #else // C-locale replacement for nl_langinfo(), restricted to the items used below. -// The constants follow the glibc/musl layout that the symbolic_range loop -// relies on: full names directly follow the abbreviated ones. +// The values are those of the POSIX locale (LC_TIME in POSIX.1, chapter 7), +// as in musl's own C locale (c_time in src/locale/langinfo.c), and in the +// same order, which the symbolic_range loop relies on: full names directly +// follow the abbreviated ones. enum { ABDAY_1 = 0, DAY_1 = 7, ABMON_1 = 14, MON_1 = 26, AM_STR = 38, PM_STR = 39, D_T_FMT = 40, D_FMT = 41, T_FMT = 42, T_FMT_AMPM = 43 }; static const char *arrow_c_langinfo[] = { From d367960d624abad6101fa23f472e136eea3a80f6 Mon Sep 17 00:00:00 2001 From: Pearu Peterson Date: Tue, 20 Oct 2026 14:12:20 +0300 Subject: [PATCH 3/3] Run the R strptime tests for locale directives on Windows Drafted by Claude Code (an AI agent) and reviewed & approved by pearu. --- r/tests/testthat/test-dplyr-funcs-datetime.R | 2 -- 1 file changed, 2 deletions(-) diff --git a/r/tests/testthat/test-dplyr-funcs-datetime.R b/r/tests/testthat/test-dplyr-funcs-datetime.R index db036bcdea11..2f99b6d75026 100644 --- a/r/tests/testthat/test-dplyr-funcs-datetime.R +++ b/r/tests/testthat/test-dplyr-funcs-datetime.R @@ -238,8 +238,6 @@ test_that("strptime works for individual formats", { collect() ) - # Some formats are not supported on Windows - skip_on_os("windows") expect_equal( strptime_test_df |> arrow_table() |>