This commit is contained in:
J. Nick Koston
2026-01-29 23:28:35 -06:00
parent 300eea034b
commit 1353dbc31e
4 changed files with 128 additions and 38 deletions

View File

@@ -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<char, 24> 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<size_t>(pos);
}
} // namespace internal
bool parse_posix_tz(const char *tz_string, ParsedTimezone &result) {

View File

@@ -2,6 +2,7 @@
#include <cstdint>
#include <ctime>
#include <span>
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<char, 24> buf);
} // namespace internal
} // namespace esphome::time

View File

@@ -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

View File

@@ -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
// ============================================================================