From 5d49c81e2d39f162484762e1b2fe25ed1be731a5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 29 Jan 2026 22:42:33 -0600 Subject: [PATCH] more cover --- esphome/core/time.cpp | 3 + tests/components/time/posix_tz_parser.cpp | 78 +++++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/esphome/core/time.cpp b/esphome/core/time.cpp index 29be550bd6..a31b863213 100644 --- a/esphome/core/time.cpp +++ b/esphome/core/time.cpp @@ -94,6 +94,9 @@ bool ESPTime::strptime(const char *time_to_parse, size_t len, ESPTime &esp_time) // HH:MM:SS (8 chars) // HH:MM (5 chars) + if (time_to_parse == nullptr || len == 0) + return false; + const char *p = time_to_parse; const char *end = time_to_parse + len; uint16_t v1, v2, v3, v4, v5, v6; diff --git a/tests/components/time/posix_tz_parser.cpp b/tests/components/time/posix_tz_parser.cpp index 7badaa37f0..99632f519d 100644 --- a/tests/components/time/posix_tz_parser.cpp +++ b/tests/components/time/posix_tz_parser.cpp @@ -379,6 +379,57 @@ TEST(PosixTzParser, MissingEndRulePlainDay) { EXPECT_FALSE(parse_posix_tz("EST5EDT,60", tz)); } +TEST(PosixTzParser, LowercaseMFormat) { + ParsedTimezone tz; + // Lowercase 'm' should be accepted + ASSERT_TRUE(parse_posix_tz("EST5EDT,m3.2.0,m11.1.0", tz)); + EXPECT_TRUE(tz.has_dst); + EXPECT_EQ(tz.dst_start.month, 3); + EXPECT_EQ(tz.dst_end.month, 11); +} + +TEST(PosixTzParser, LowercaseJFormat) { + ParsedTimezone tz; + // Lowercase 'j' should be accepted + ASSERT_TRUE(parse_posix_tz("EST5EDT,j60,j305", tz)); + EXPECT_EQ(tz.dst_start.type, DSTRuleType::JULIAN_NO_LEAP); + EXPECT_EQ(tz.dst_start.day, 60); +} + +TEST(PosixTzParser, DstNameWithoutRules) { + ParsedTimezone tz; + // DST name present but no rules - should have has_dst=true with default offset + ASSERT_TRUE(parse_posix_tz("EST5EDT", tz)); + EXPECT_TRUE(tz.has_dst); + EXPECT_EQ(tz.std_offset_seconds, 5 * 3600); + EXPECT_EQ(tz.dst_offset_seconds, 4 * 3600); // Default: std - 1 hour +} + +TEST(PosixTzParser, TrailingCharactersIgnored) { + ParsedTimezone tz; + // Trailing characters after valid TZ should be ignored (parser stops at end of valid input) + // This matches libc behavior + ASSERT_TRUE(parse_posix_tz("EST5", tz)); + EXPECT_EQ(tz.std_offset_seconds, 5 * 3600); +} + +TEST(PosixTzParser, PlainDay365LeapYear) { + // Day 365 in leap year is Dec 31 + int month, day; + internal::day_of_year_to_month_day(365, 2024, month, day); + EXPECT_EQ(month, 12); + 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 + 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 +} + // ============================================================================ // Large offset tests // ============================================================================ @@ -617,6 +668,28 @@ TEST(PosixTzParser, EpochToLocalBasic) { EXPECT_EQ(local.tm_hour, 0); } +TEST(PosixTzParser, EpochToLocalNegativeEpoch) { + ParsedTimezone tz; + parse_posix_tz("UTC0", tz); + + // Dec 31, 1969 23:59:59 UTC (1 second before epoch) + time_t epoch = -1; + struct tm local; + ASSERT_TRUE(epoch_to_local_tm(epoch, tz, &local)); + EXPECT_EQ(local.tm_year, 69); // 1969 + EXPECT_EQ(local.tm_mon, 11); // December + EXPECT_EQ(local.tm_mday, 31); + EXPECT_EQ(local.tm_hour, 23); + EXPECT_EQ(local.tm_min, 59); + EXPECT_EQ(local.tm_sec, 59); +} + +TEST(PosixTzParser, EpochToLocalNullTmFails) { + ParsedTimezone tz; + parse_posix_tz("UTC0", tz); + EXPECT_FALSE(epoch_to_local_tm(0, tz, nullptr)); +} + TEST(PosixTzParser, EpochToLocalWithOffset) { ParsedTimezone tz; parse_posix_tz("EST5", tz); // UTC-5 @@ -832,6 +905,11 @@ TEST(ESPTimeStrptime, EmptyStringFails) { EXPECT_FALSE(ESPTime::strptime("", 0, t)); } +TEST(ESPTimeStrptime, NullInputFails) { + ESPTime t{}; + EXPECT_FALSE(ESPTime::strptime(nullptr, 0, t)); +} + TEST(ESPTimeStrptime, InvalidFormatFails) { ESPTime t{}; EXPECT_FALSE(ESPTime::strptime("not-a-date", 10, t));