[time] Fix RTC is_valid() rejecting valid times after day_of_year cleanup (#15763)

This commit is contained in:
J. Nick Koston
2026-04-16 03:40:22 -10:00
committed by Jesse Hills
parent 914ed10bcc
commit aa80bdbbc6
7 changed files with 83 additions and 7 deletions
+1 -1
View File
@@ -63,7 +63,7 @@ void BM8563::read_time() {
rtc_time.day_of_week, rtc_time.hour, rtc_time.minute, rtc_time.second);
rtc_time.recalc_timestamp_utc(false);
if (!rtc_time.is_valid()) {
if (!rtc_time.is_valid(/*check_day_of_week=*/true, /*check_day_of_year=*/false)) {
ESP_LOGE(TAG, "Invalid RTC time, not syncing to system clock.");
return;
}
+1 -1
View File
@@ -44,7 +44,7 @@ void DS1307Component::read_time() {
.year = uint16_t(ds1307_.reg.year + 10u * ds1307_.reg.year_10 + 2000),
};
rtc_time.recalc_timestamp_utc(false);
if (!rtc_time.is_valid()) {
if (!rtc_time.is_valid(/*check_day_of_week=*/true, /*check_day_of_year=*/false)) {
ESP_LOGE(TAG, "Invalid RTC time, not syncing to system clock.");
return;
}
+1 -1
View File
@@ -44,7 +44,7 @@ void PCF85063Component::read_time() {
.year = uint16_t(pcf85063_.reg.year + 10u * pcf85063_.reg.year_10 + 2000),
};
rtc_time.recalc_timestamp_utc(false);
if (!rtc_time.is_valid()) {
if (!rtc_time.is_valid(/*check_day_of_week=*/true, /*check_day_of_year=*/false)) {
ESP_LOGE(TAG, "Invalid RTC time, not syncing to system clock.");
return;
}
+1 -1
View File
@@ -44,7 +44,7 @@ void PCF8563Component::read_time() {
.year = uint16_t(pcf8563_.reg.year + 10u * pcf8563_.reg.year_10 + 2000),
};
rtc_time.recalc_timestamp_utc(false);
if (!rtc_time.is_valid()) {
if (!rtc_time.is_valid(/*check_day_of_week=*/true, /*check_day_of_year=*/false)) {
ESP_LOGE(TAG, "Invalid RTC time, not syncing to system clock.");
return;
}
+1 -1
View File
@@ -81,7 +81,7 @@ void RX8130Component::read_time() {
.year = static_cast<uint16_t>(bcd2dec(date[6]) + 2000),
};
rtc_time.recalc_timestamp_utc(false);
if (!rtc_time.is_valid()) {
if (!rtc_time.is_valid(/*check_day_of_week=*/true, /*check_day_of_year=*/false)) {
ESP_LOGE(TAG, "Invalid RTC time, not syncing to system clock.");
return;
}
+6 -2
View File
@@ -76,8 +76,12 @@ struct ESPTime {
/// @copydoc strftime(const std::string &format)
std::string strftime(const char *format);
/// Check if this ESPTime is valid (all fields in range and year is greater than or equal to 2019)
bool is_valid() const { return this->year >= 2019 && this->fields_in_range(); }
/// Check if this ESPTime is valid (year >= 2019 and the requested fields are in range).
/// @param check_day_of_week validate day_of_week (not always available when constructing from date/time fields)
/// @param check_day_of_year validate day_of_year (not always available when constructing from date/time fields)
bool is_valid(bool check_day_of_week = true, bool check_day_of_year = true) const {
return this->year >= 2019 && this->fields_in_range(check_day_of_week, check_day_of_year);
}
/// Check if time fields are in range.
/// @param check_day_of_week validate day_of_week (not always available when constructing from date/time fields)
+72
View File
@@ -0,0 +1,72 @@
// Regression tests for ESPTime::is_valid() optional checks.
//
// The RTC components (ds1307, bm8563, pcf85063, pcf8563, rx8130) read date/time
// fields from hardware but do not populate day_of_year. They call
// recalc_timestamp_utc(false) -- which skips day_of_year -- and then is_valid().
// These tests ensure the is_valid() overload can skip day_of_year validation so
// RTCs don't log "Invalid RTC time, not syncing to system clock." for valid times.
#include <gtest/gtest.h>
#include "esphome/core/time.h"
namespace esphome::testing {
// Build an ESPTime that mirrors what the RTC components construct: all fields
// populated from hardware except day_of_year (left zero-initialized).
static ESPTime make_rtc_like_time() {
ESPTime t{};
t.second = 30;
t.minute = 15;
t.hour = 12;
t.day_of_week = 4; // thursday
t.day_of_month = 15;
t.month = 4;
t.year = 2026;
// day_of_year intentionally left at 0 -- RTCs don't compute it.
return t;
}
TEST(ESPTimeIsValid, DefaultRejectsZeroDayOfYear) {
// Default is_valid() checks day_of_year; zero-init is out of range.
ESPTime t = make_rtc_like_time();
EXPECT_FALSE(t.is_valid());
}
TEST(ESPTimeIsValid, SkipDayOfYearAcceptsRTCLikeTime) {
// RTC code path: skip day_of_year validation.
ESPTime t = make_rtc_like_time();
EXPECT_TRUE(t.is_valid(/*check_day_of_week=*/true, /*check_day_of_year=*/false));
}
TEST(ESPTimeIsValid, SkipDayOfYearStillRejectsOutOfRangeFields) {
ESPTime t = make_rtc_like_time();
t.hour = 25;
EXPECT_FALSE(t.is_valid(/*check_day_of_week=*/true, /*check_day_of_year=*/false));
}
TEST(ESPTimeIsValid, SkipDayOfYearStillRejectsYearBefore2019) {
ESPTime t = make_rtc_like_time();
t.year = 2000;
EXPECT_FALSE(t.is_valid(/*check_day_of_week=*/true, /*check_day_of_year=*/false));
}
TEST(ESPTimeIsValid, SkipBothDayChecksAcceptsGPSLikeTime) {
// GPS path (gps_time.cpp) populates neither day_of_week nor day_of_year.
ESPTime t{};
t.second = 30;
t.minute = 15;
t.hour = 12;
t.day_of_month = 15;
t.month = 4;
t.year = 2026;
EXPECT_TRUE(t.is_valid(/*check_day_of_week=*/false, /*check_day_of_year=*/false));
EXPECT_FALSE(t.is_valid()); // default still rejects
}
TEST(ESPTimeIsValid, FullyPopulatedAcceptsWithDefaults) {
ESPTime t = make_rtc_like_time();
t.day_of_year = 105;
EXPECT_TRUE(t.is_valid());
}
} // namespace esphome::testing