diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 4437617354..f0f7f4a3b1 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -867,7 +867,7 @@ message GetTimeResponse { option (no_delay) = true; fixed32 epoch_seconds = 1; - string timezone = 2; + string timezone = 2 [deprecated = true]; // Use parsed_timezone instead. Remove before 2026.9.0. ParsedTimezone parsed_timezone = 3; } diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 8f4aabfa21..8747e19270 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1112,24 +1112,31 @@ void APIConnection::on_get_time_response(const GetTimeResponse &value) { if (homeassistant::global_homeassistant_time != nullptr) { homeassistant::global_homeassistant_time->set_epoch_time(value.epoch_seconds); #ifdef USE_TIME_TIMEZONE - if (!value.timezone.empty()) { + // Only apply if the sender provided pre-parsed timezone data. + // Old clients (before 2026.3.0) only send the timezone string without the parsed struct, + // so all parsed_timezone fields default to zero — skip to keep the codegen-configured timezone. + // For actual UTC (all zeros), this also skips, which is harmless since UTC is the default. + // Eventually the timezone string will be removed and only the struct will be sent. + { const auto &pt = value.parsed_timezone; - time::ParsedTimezone tz{}; - tz.std_offset_seconds = pt.std_offset_seconds; - tz.dst_offset_seconds = pt.dst_offset_seconds; - tz.dst_start.time_seconds = pt.dst_start.time_seconds; - tz.dst_start.day = static_cast(pt.dst_start.day); - tz.dst_start.type = static_cast(pt.dst_start.type); - tz.dst_start.month = static_cast(pt.dst_start.month); - tz.dst_start.week = static_cast(pt.dst_start.week); - tz.dst_start.day_of_week = static_cast(pt.dst_start.day_of_week); - tz.dst_end.time_seconds = pt.dst_end.time_seconds; - tz.dst_end.day = static_cast(pt.dst_end.day); - tz.dst_end.type = static_cast(pt.dst_end.type); - tz.dst_end.month = static_cast(pt.dst_end.month); - tz.dst_end.week = static_cast(pt.dst_end.week); - tz.dst_end.day_of_week = static_cast(pt.dst_end.day_of_week); - time::set_global_tz(tz); + if (pt.std_offset_seconds != 0 || pt.dst_start.type != enums::DST_RULE_TYPE_NONE) { + time::ParsedTimezone tz{}; + tz.std_offset_seconds = pt.std_offset_seconds; + tz.dst_offset_seconds = pt.dst_offset_seconds; + tz.dst_start.time_seconds = pt.dst_start.time_seconds; + tz.dst_start.day = static_cast(pt.dst_start.day); + tz.dst_start.type = static_cast(pt.dst_start.type); + tz.dst_start.month = static_cast(pt.dst_start.month); + tz.dst_start.week = static_cast(pt.dst_start.week); + tz.dst_start.day_of_week = static_cast(pt.dst_start.day_of_week); + tz.dst_end.time_seconds = pt.dst_end.time_seconds; + tz.dst_end.day = static_cast(pt.dst_end.day); + tz.dst_end.type = static_cast(pt.dst_end.type); + tz.dst_end.month = static_cast(pt.dst_end.month); + tz.dst_end.week = static_cast(pt.dst_end.week); + tz.dst_end.day_of_week = static_cast(pt.dst_end.day_of_week); + time::set_global_tz(tz); + } } #endif } diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 9e74d5ddc7..3f23943b12 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -1007,10 +1007,6 @@ bool ParsedTimezone::decode_length(uint32_t field_id, ProtoLengthDelimited value } bool GetTimeResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 2: { - this->timezone = StringRef(reinterpret_cast(value.data()), value.size()); - break; - } case 3: value.decode_to_message(this->parsed_timezone); break; diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 37ed14415c..a712be4637 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1153,12 +1153,11 @@ class ParsedTimezone final : public ProtoDecodableMessage { class GetTimeResponse final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 37; - static constexpr uint8_t ESTIMATED_SIZE = 31; + static constexpr uint8_t ESTIMATED_SIZE = 22; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "get_time_response"; } #endif uint32_t epoch_seconds{0}; - StringRef timezone{}; ParsedTimezone parsed_timezone{}; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index 290e5fe4d2..869892c691 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -1291,7 +1291,6 @@ const char *ParsedTimezone::dump_to(DumpBuffer &out) const { const char *GetTimeResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "GetTimeResponse"); dump_field(out, "epoch_seconds", this->epoch_seconds); - dump_field(out, "timezone", this->timezone); out.append(" parsed_timezone: "); this->parsed_timezone.dump_to(out); out.append("\n");