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(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(result.tm_year) + 1900; + if (year < static_cast(arrow_vendored::date::year::min()) || + year > static_cast(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(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";