diff --git a/cpp/src/arrow/util/value_parsing.h b/cpp/src/arrow/util/value_parsing.h index 195cdc843ac..2bea84f9e4c 100644 --- a/cpp/src/arrow/util/value_parsing.h +++ b/cpp/src/arrow/util/value_parsing.h @@ -826,9 +826,16 @@ 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; + } // ignore the time part arrow_vendored::date::sys_seconds secs = - arrow_vendored::date::sys_days(arrow_vendored::date::year(result.tm_year + 1900) / + arrow_vendored::date::sys_days(arrow_vendored::date::year(static_cast(year)) / (result.tm_mon + 1) / std::max(result.tm_mday, 1)); if (!ignore_time_in_day) { secs += (std::chrono::hours(result.tm_hour) + std::chrono::minutes(result.tm_min) + diff --git a/cpp/src/arrow/util/value_parsing_test.cc b/cpp/src/arrow/util/value_parsing_test.cc index b61f777685b..7be657f822e 100644 --- a/cpp/src/arrow/util/value_parsing_test.cc +++ b/cpp/src/arrow/util/value_parsing_test.cc @@ -876,6 +876,25 @@ TEST(TimestampParser, StrptimeParser) { } } +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";