diff --git a/cpp/src/arrow/util/value_parsing.h b/cpp/src/arrow/util/value_parsing.h index 195cdc843ac6..41ab68969b7f 100644 --- a/cpp/src/arrow/util/value_parsing.h +++ b/cpp/src/arrow/util/value_parsing.h @@ -826,10 +826,12 @@ static inline bool ParseTimestampStrptime(const char* buf, size_t length, if (!allow_trailing_chars && static_cast(ret - clean_copy.c_str()) != length) { 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) / - (result.tm_mon + 1) / std::max(result.tm_mday, 1)); + const auto ymd = arrow_vendored::date::year(result.tm_year + 1900) / + (result.tm_mon + 1) / std::max(result.tm_mday, 1); + if (ARROW_PREDICT_FALSE(!ymd.ok())) { + return false; + } + arrow_vendored::date::sys_seconds secs = arrow_vendored::date::sys_days(ymd); if (!ignore_time_in_day) { secs += (std::chrono::hours(result.tm_hour) + std::chrono::minutes(result.tm_min) + std::chrono::seconds(result.tm_sec)); diff --git a/cpp/src/arrow/util/value_parsing_test.cc b/cpp/src/arrow/util/value_parsing_test.cc index b61f777685b7..d19726680d33 100644 --- a/cpp/src/arrow/util/value_parsing_test.cc +++ b/cpp/src/arrow/util/value_parsing_test.cc @@ -876,6 +876,27 @@ TEST(TimestampParser, StrptimeParser) { } } +TEST(TimestampParser, StrptimeParserCalendarDateValidation) { + std::string format = "%Y-%m-%d"; + auto parser = TimestampParser::MakeStrptime(format); + + for (const std::string& value : {"1999-02-30", "1999-02-29", "1900-02-29", + "2024-04-31"}) { + SCOPED_TRACE(value); + int64_t converted; + ASSERT_FALSE((*parser)(value.c_str(), value.size(), TimeUnit::SECOND, &converted)); + } + + for (const std::string& value : {"2000-02-29", "2024-04-30"}) { + SCOPED_TRACE(value); + int64_t converted, expected; + ASSERT_TRUE((*parser)(value.c_str(), value.size(), TimeUnit::SECOND, &converted)); + ASSERT_TRUE(ParseTimestampISO8601(value.c_str(), value.size(), TimeUnit::SECOND, + &expected)); + ASSERT_EQ(expected, converted); + } +} + TEST(TimestampParser, StrptimeZoneOffset) { if (!kStrptimeSupportsZone) { GTEST_SKIP() << "strptime does not support %z on this platform";