This is an automated email from the ASF dual-hosted git repository.

kou pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git


The following commit(s) were added to refs/heads/main by this push:
     new b9479757dd5 GH-54646: [C++] Reject years out of range in the strptime 
timestamp parser (#54658)
b9479757dd5 is described below

commit b9479757dd5c1914c2b007f11f6679c4fc080c05
Author: Pearu Peterson <[email protected]>
AuthorDate: Thu Oct 22 17:22:24 2026 +0300

    GH-54646: [C++] Reject years out of range in the strptime timestamp parser 
(#54658)
    
    ### Rationale for this change
    
    The parser computed the year as `tm_year + 1900`, which can overflow `int` 
(e.g. with `%s`), and passed it to `date::year`, which holds 16 bits, so a year 
out of range gave a wrong timestamp.
    
    ### What changes are included in this PR?
    
    The year is computed in 64 bits and rejected if `date::year` cannot hold it.
    
    ### Are these changes tested?
    
    Yes, `TimestampParser.StrptimeYearOutOfRange` covers a year that wraps 
around and one where `tm_year + 1900` overflows `int`. It needs `%s` and is 
skipped where `strptime` does not parse `%s` into 64 bits (e.g. on Windows).
    
    ### Are there any user-facing changes?
    
    Values whose year does not fit in 16 bits no longer parse with `strptime` 
formats.
    
    ### AI usage disclosure
    
    Developed with AI assistance (Claude Code): the fix, the test and this 
description were AI-generated under my direction and reviewed line by line by 
me. I own and can debug these changes. No copyrighted material is included.
    
    * GitHub Issue: #54646
    
    Authored-by: Pearu Peterson <[email protected]>
    Signed-off-by: Sutou Kouhei <[email protected]>
---
 cpp/src/arrow/util/value_parsing.h       |  9 ++++++++-
 cpp/src/arrow/util/value_parsing_test.cc | 19 +++++++++++++++++++
 2 files changed, 27 insertions(+), 1 deletion(-)

diff --git a/cpp/src/arrow/util/value_parsing.h 
b/cpp/src/arrow/util/value_parsing.h
index 1b300074851..1c2102fe370 100644
--- a/cpp/src/arrow/util/value_parsing.h
+++ b/cpp/src/arrow/util/value_parsing.h
@@ -829,11 +829,18 @@ static inline bool ParseTimestampStrptime(const char* 
buf, size_t length,
   if (!allow_trailing_chars && static_cast<size_t>(ret - clean_copy.c_str()) 
!= length) {
     return false;
   }
+  // tm_year + 1900 can overflow int (e.g. with %s), and date::year only
+  // holds 16 bits
+  const int64_t year = static_cast<int64_t>(result.tm_year) + 1900;
+  if (year < static_cast<int>(arrow_vendored::date::year::min()) ||
+      year > static_cast<int>(arrow_vendored::date::year::max())) {
+    return false;
+  }
   // Reject impossible dates such as February 30 or a zero day, which strptime
   // may accept (the day of month is checked against 31 at most), instead of
   // rolling them over into the next month; consistent with the ISO-8601 
parser.
   const arrow_vendored::date::year_month_day ymd =
-      arrow_vendored::date::year(result.tm_year + 1900) / (result.tm_mon + 1) /
+      arrow_vendored::date::year(static_cast<int>(year)) / (result.tm_mon + 1) 
/
       result.tm_mday;
   if (!ymd.ok()) {
     return false;
diff --git a/cpp/src/arrow/util/value_parsing_test.cc 
b/cpp/src/arrow/util/value_parsing_test.cc
index 69baba169ff..875418f3195 100644
--- a/cpp/src/arrow/util/value_parsing_test.cc
+++ b/cpp/src/arrow/util/value_parsing_test.cc
@@ -928,6 +928,25 @@ TEST(TimestampParser, StrptimeWithoutDay) {
   ASSERT_EQ(expected, converted);
 }
 
+TEST(TimestampParser, StrptimeYearOutOfRange) {
+  auto parser = TimestampParser::MakeStrptime("%s");
+  int64_t converted = 0;
+  // A value past 2038 (2100-01-01), as a strptime that parses %s into a 32-bit
+  // time_t wraps larger values into ordinary dates
+  if (!(*parser)("4102444800", 10, TimeUnit::SECOND, &converted) ||
+      converted != 4102444800) {
+    GTEST_SKIP() << "strptime does not support 64-bit %s values on this 
platform";
+  }
+
+  // Years that do not fit in a date::year are rejected rather than wrapped,
+  // also where tm_year + 1900 would overflow int
+  for (const std::string value :
+       {"1000000000000000", "67768036000000000", "67768036191676799"}) {
+    SCOPED_TRACE(value);
+    ASSERT_FALSE((*parser)(value.c_str(), value.size(), TimeUnit::SECOND, 
&converted));
+  }
+}
+
 TEST(TimestampParser, StrptimeZoneOffset) {
   if (!kStrptimeSupportsZone) {
     GTEST_SKIP() << "strptime does not support %z on this platform";

Reply via email to