From 1353dbc31ea06d82f61da5fbfa6a5cc4d768003d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 29 Jan 2026 23:28:35 -0600 Subject: [PATCH] cleanup --- esphome/components/time/posix_tz.cpp | 35 +++++++++ esphome/components/time/posix_tz.h | 7 ++ esphome/components/time/real_time_clock.cpp | 40 +--------- tests/components/time/posix_tz_parser.cpp | 84 +++++++++++++++++++++ 4 files changed, 128 insertions(+), 38 deletions(-) diff --git a/esphome/components/time/posix_tz.cpp b/esphome/components/time/posix_tz.cpp index d400cce454c..d54e6114432 100644 --- a/esphome/components/time/posix_tz.cpp +++ b/esphome/components/time/posix_tz.cpp @@ -342,6 +342,41 @@ bool __attribute__((noinline)) is_in_dst(time_t utc_epoch, const ParsedTimezone } } +size_t format_dst_rule(const DSTRule &rule, std::span buf) { + // Format rule part + int pos = 0; + switch (rule.type) { + case DSTRuleType::MONTH_WEEK_DAY: + pos = snprintf(buf.data(), buf.size(), "M%d.%d.%d", rule.month, rule.week, rule.day_of_week); + break; + case DSTRuleType::JULIAN_NO_LEAP: + pos = snprintf(buf.data(), buf.size(), "J%d", rule.day); + break; + case DSTRuleType::DAY_OF_YEAR: + pos = snprintf(buf.data(), buf.size(), "%d", rule.day); + break; + } + + // Format time part + int32_t time_secs = rule.time_seconds; + char sign = time_secs < 0 ? '-' : '/'; + if (time_secs < 0) + time_secs = -time_secs; + int hours = time_secs / 3600; + int mins = (time_secs % 3600) / 60; + int secs = time_secs % 60; + + if (secs != 0) { + pos += snprintf(buf.data() + pos, buf.size() - pos, "%c%d:%02d:%02d", sign, hours, mins, secs); + } else if (mins != 0) { + pos += snprintf(buf.data() + pos, buf.size() - pos, "%c%d:%02d", sign, hours, mins); + } else { + pos += snprintf(buf.data() + pos, buf.size() - pos, "%c%d", sign, hours); + } + + return static_cast(pos); +} + } // namespace internal bool parse_posix_tz(const char *tz_string, ParsedTimezone &result) { diff --git a/esphome/components/time/posix_tz.h b/esphome/components/time/posix_tz.h index ea9864b3045..fa2e7f8ab68 100644 --- a/esphome/components/time/posix_tz.h +++ b/esphome/components/time/posix_tz.h @@ -2,6 +2,7 @@ #include #include +#include namespace esphome::time { @@ -112,6 +113,12 @@ time_t calculate_dst_transition(int year, const DSTRule &rule, int32_t base_offs /// @return true if DST is in effect at the given time bool is_in_dst(time_t utc_epoch, const ParsedTimezone &tz); +/// Format a DST rule for logging/display +/// @param rule The DST rule to format +/// @param buf Output buffer (24 bytes recommended) +/// @return Number of characters written (excluding null terminator) +size_t format_dst_rule(const DSTRule &rule, std::span buf); + } // namespace internal } // namespace esphome::time diff --git a/esphome/components/time/real_time_clock.cpp b/esphome/components/time/real_time_clock.cpp index 870d689195e..649cacd3f05 100644 --- a/esphome/components/time/real_time_clock.cpp +++ b/esphome/components/time/real_time_clock.cpp @@ -23,42 +23,6 @@ static const char *const TAG = "time"; RealTimeClock::RealTimeClock() = default; -// Helper to format a DST rule for logging -#ifdef USE_TIME_TIMEZONE -static void format_dst_rule(const DSTRule &rule, char *buf, size_t buf_size) { - // Format rule part - int pos = 0; - switch (rule.type) { - case DSTRuleType::MONTH_WEEK_DAY: - pos = snprintf(buf, buf_size, "M%d.%d.%d", rule.month, rule.week, rule.day_of_week); - break; - case DSTRuleType::JULIAN_NO_LEAP: - pos = snprintf(buf, buf_size, "J%d", rule.day); - break; - case DSTRuleType::DAY_OF_YEAR: - pos = snprintf(buf, buf_size, "%d", rule.day); - break; - } - - // Format time part - int32_t time_secs = rule.time_seconds; - char sign = time_secs < 0 ? '-' : '/'; - if (time_secs < 0) - time_secs = -time_secs; - int hours = time_secs / 3600; - int mins = (time_secs % 3600) / 60; - int secs = time_secs % 60; - - if (secs != 0) { - snprintf(buf + pos, buf_size - pos, "%c%d:%02d:%02d", sign, hours, mins, secs); - } else if (mins != 0) { - snprintf(buf + pos, buf_size - pos, "%c%d:%02d", sign, hours, mins); - } else { - snprintf(buf + pos, buf_size - pos, "%c%d", sign, hours); - } -} -#endif - void RealTimeClock::dump_config() { #ifdef USE_TIME_TIMEZONE int std_hours = -this->parsed_tz_.std_offset_seconds / 3600; @@ -67,8 +31,8 @@ void RealTimeClock::dump_config() { if (this->parsed_tz_.has_dst) { int dst_hours = -this->parsed_tz_.dst_offset_seconds / 3600; char start_buf[24], end_buf[24]; - format_dst_rule(this->parsed_tz_.dst_start, start_buf, sizeof(start_buf)); - format_dst_rule(this->parsed_tz_.dst_end, end_buf, sizeof(end_buf)); + internal::format_dst_rule(this->parsed_tz_.dst_start, start_buf); + internal::format_dst_rule(this->parsed_tz_.dst_end, end_buf); ESP_LOGCONFIG(TAG, " DST: UTC%+d, %s - %s", dst_hours, start_buf, end_buf); } #endif diff --git a/tests/components/time/posix_tz_parser.cpp b/tests/components/time/posix_tz_parser.cpp index 1a84a398357..fb3af1ae351 100644 --- a/tests/components/time/posix_tz_parser.cpp +++ b/tests/components/time/posix_tz_parser.cpp @@ -831,6 +831,90 @@ INSTANTIATE_TEST_SUITE_P(AustraliaSydney, LibcVerificationTest, std::make_tuple("AEST-10AEDT,M10.1.0,M4.1.0/3", 1720000000), std::make_tuple("AEST-10AEDT,M10.1.0,M4.1.0/3", 1735689600))); +// ============================================================================ +// format_dst_rule tests +// ============================================================================ + +TEST(PosixTzParser, FormatDstRuleMonthWeekDay) { + DSTRule rule{}; + rule.type = DSTRuleType::MONTH_WEEK_DAY; + rule.month = 3; + rule.week = 2; + rule.day_of_week = 0; + rule.time_seconds = 2 * 3600; // 2:00 + + char buf[24]; + size_t len = internal::format_dst_rule(rule, buf); + EXPECT_STREQ(buf, "M3.2.0/2"); + EXPECT_EQ(len, 8u); +} + +TEST(PosixTzParser, FormatDstRuleJulian) { + DSTRule rule{}; + rule.type = DSTRuleType::JULIAN_NO_LEAP; + rule.day = 60; + rule.time_seconds = 2 * 3600; + + char buf[24]; + size_t len = internal::format_dst_rule(rule, buf); + EXPECT_STREQ(buf, "J60/2"); + EXPECT_EQ(len, 5u); +} + +TEST(PosixTzParser, FormatDstRuleDayOfYear) { + DSTRule rule{}; + rule.type = DSTRuleType::DAY_OF_YEAR; + rule.day = 300; + rule.time_seconds = 2 * 3600; + + char buf[24]; + size_t len = internal::format_dst_rule(rule, buf); + EXPECT_STREQ(buf, "300/2"); + EXPECT_EQ(len, 5u); +} + +TEST(PosixTzParser, FormatDstRuleWithMinutes) { + DSTRule rule{}; + rule.type = DSTRuleType::MONTH_WEEK_DAY; + rule.month = 11; + rule.week = 1; + rule.day_of_week = 0; + rule.time_seconds = 2 * 3600 + 30 * 60; // 2:30 + + char buf[24]; + size_t len = internal::format_dst_rule(rule, buf); + EXPECT_STREQ(buf, "M11.1.0/2:30"); + EXPECT_EQ(len, 12u); +} + +TEST(PosixTzParser, FormatDstRuleWithSeconds) { + DSTRule rule{}; + rule.type = DSTRuleType::MONTH_WEEK_DAY; + rule.month = 3; + rule.week = 5; + rule.day_of_week = 0; + rule.time_seconds = 2 * 3600 + 30 * 60 + 45; // 2:30:45 + + char buf[24]; + size_t len = internal::format_dst_rule(rule, buf); + EXPECT_STREQ(buf, "M3.5.0/2:30:45"); + EXPECT_EQ(len, 14u); +} + +TEST(PosixTzParser, FormatDstRuleNegativeTime) { + DSTRule rule{}; + rule.type = DSTRuleType::MONTH_WEEK_DAY; + rule.month = 3; + rule.week = 2; + rule.day_of_week = 0; + rule.time_seconds = -1 * 3600; // -1:00 (11 PM previous day) + + char buf[24]; + size_t len = internal::format_dst_rule(rule, buf); + EXPECT_STREQ(buf, "M3.2.0-1"); + EXPECT_EQ(len, 8u); +} + // ============================================================================ // DST boundary edge cases // ============================================================================