diff --git a/esphome/components/time/posix_tz.cpp b/esphome/components/time/posix_tz.cpp index bbceb057a0..1de3504072 100644 --- a/esphome/components/time/posix_tz.cpp +++ b/esphome/components/time/posix_tz.cpp @@ -274,6 +274,8 @@ static int __attribute__((noinline)) days_from_year_start(int year, int month, i } // Calculate days from epoch to Jan 1 of given year +// Note: Only valid for years >= 1970. Pre-1970 timestamps are not supported +// as they are extremely rare for IoT devices. static int64_t __attribute__((noinline)) days_to_year_start(int year) { int64_t days = 0; for (int y = 1970; y < year; y++) { diff --git a/esphome/core/time.cpp b/esphome/core/time.cpp index aa8dba4b6f..e59c33dc23 100644 --- a/esphome/core/time.cpp +++ b/esphome/core/time.cpp @@ -86,6 +86,12 @@ static bool expect_char(const char *&p, const char *end, char expected) { return true; } +// Helper to skip trailing whitespace (for backward compatibility with sscanf) +static void skip_trailing_whitespace(const char *&p, const char *end) { + while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) + p++; +} + bool ESPTime::strptime(const char *time_to_parse, size_t len, ESPTime &esp_time) { // Supported formats: // YYYY-MM-DD HH:MM:SS (19 chars) @@ -150,6 +156,7 @@ bool ESPTime::strptime(const char *time_to_parse, size_t len, ESPTime &esp_time) return false; esp_time.second = v6; + skip_trailing_whitespace(p, end); return p == end; // YYYY-MM-DD HH:MM:SS } @@ -177,6 +184,7 @@ bool ESPTime::strptime(const char *time_to_parse, size_t len, ESPTime &esp_time) return false; esp_time.second = v3; + skip_trailing_whitespace(p, end); return p == end; // HH:MM:SS } diff --git a/tests/components/time/posix_tz_parser.cpp b/tests/components/time/posix_tz_parser.cpp index 4a276d7b71..320eec821a 100644 --- a/tests/components/time/posix_tz_parser.cpp +++ b/tests/components/time/posix_tz_parser.cpp @@ -421,13 +421,12 @@ TEST(PosixTzParser, PlainDay365LeapYear) { EXPECT_EQ(day, 31); } -TEST(PosixTzParser, PlainDay365NonLeapYear) { - // Day 365 in non-leap year would be Jan 1 of next year (out of range) - // But our function should handle it gracefully +TEST(PosixTzParser, PlainDay364NonLeapYear) { + // Day 364 (0-indexed) is Dec 31 in non-leap year (last valid day) int month, day; internal::day_of_year_to_month_day(364, 2025, month, day); EXPECT_EQ(month, 12); - EXPECT_EQ(day, 31); // Day 364 is Dec 31 in non-leap year + EXPECT_EQ(day, 31); } // ============================================================================