Old clients (before 2026.3.0) send only the timezone string without the
parsed_timezone struct, so all fields default to zero. Without this check,
the device would overwrite its codegen-configured timezone with UTC.
Keep the codegen timezone when the struct is unpopulated (all zeros).
For actual UTC this also skips, which is harmless since UTC is the default.
Move tests that use make_us_central(), set_global_tz(), ParsedTimezone,
and DSTRuleType into esphome::time::testing namespace where those symbols
are declared.
Remove the runtime POSIX TZ string parser and all associated bridge code
now that timezone data is sent as pre-parsed structs via protobuf.
Removed:
- parse_posix_tz() and internal parsing helpers (skip_tz_name, parse_offset,
parse_dst_rule, parse_uint, parse_transition_time)
- RealTimeClock::set_timezone() overloads and apply_timezone_()
- API connection fallback path for string-based timezone
Kept:
- All conversion functions (epoch_to_local_tm, is_in_dst, calculate_dst_transition)
- Internal helpers used by conversion functions
- localtime_r/localtime overrides
- Tests for all permanent functions
On ESP8266, .rodata is mapped to DRAM (RAM), not flash. When
StructInitializer is used with all compile-time constant fields,
the compiler places the entire struct as a const blob in .rodata,
silently consuming ~20 bytes of RAM.
Switch to field-by-field assignment on ESP8266 so the IP address
values are encoded as immediate operands in flash instructions
instead of a .rodata blob. Other platforms continue to use the
aggregate initializer since their .rodata is flash-mapped.
Fixes redeclaration error when multiple time platforms are in the
same build by wrapping the local ParsedTimezone variable in a scope block.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The C++ POSIX TZ string parser is only needed for backward compatibility
with older Home Assistant clients that send the timezone as a string.
Once all clients send the pre-parsed ParsedTimezone protobuf struct,
the parser and its helpers can be removed entirely.
See https://github.com/esphome/backlog/issues/91
build_info_data.h contains ESPHOME_BUILD_TIME which changes every build.
Since application.h included it, changing any source file caused
build_info_data.h to be rewritten (via sources_changed), which then
triggered a rebuild of every file that includes application.h.
Move all build_info_data.h dependent code from application.h inline
methods to application.cpp, so only application.cpp recompiles when
build info changes.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
build_info_data.h contains ESPHOME_BUILD_TIME which changes every build.
Since application.h included it, changing any source file caused
build_info_data.h to be rewritten (via sources_changed), which then
triggered a rebuild of every file that includes application.h.
Move all build_info_data.h dependent code from application.h inline
methods to application.cpp, so only application.cpp recompiles when
build info changes.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
build_info_data.h contains ESPHOME_BUILD_TIME which changes every build.
Since application.h included it, changing any source file caused
build_info_data.h to be rewritten (via sources_changed), which then
triggered a rebuild of every file that includes application.h.
Move all build_info_data.h dependent code from application.h inline
methods to application.cpp, so only application.cpp recompiles when
build info changes.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
build_info_data.h contains ESPHOME_BUILD_TIME which changes every build.
Since application.h included it, changing any source file caused
build_info_data.h to be rewritten (via sources_changed), which then
triggered a rebuild of every file that includes application.h.
Move all build_info_data.h dependent code from application.h inline
methods to application.cpp, so only application.cpp recompiles when
build info changes.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
build_info_data.h contains ESPHOME_BUILD_TIME which changes every build.
Since application.h included it, changing any source file caused
build_info_data.h to be rewritten (via sources_changed), which then
triggered a rebuild of every file that includes application.h.
Move all build_info_data.h dependent code from application.h inline
methods to application.cpp, so only application.cpp recompiles when
build info changes.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
If CLOSE_EVT never arrives after DISCONNECT_EVT, the client gets
stuck in DISCONNECTING forever, blocking reconnection and scanner
restart. Add a 10s timeout watchdog in loop() that forces IDLE
as a recovery path.
Introduce set_disconnecting_() helper to ensure the timeout
timestamp is always set when entering DISCONNECTING state.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Separate the DISCONNECTING state into its own log message that
mentions waiting for CLOSE_EVT, making it easier to diagnose
stuck connections vs normal in-progress connections.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Separate the DISCONNECTING state into its own log message that
mentions waiting for CLOSE_EVT, making it easier to diagnose
stuck connections vs normal in-progress connections.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The CLOSE_EVT handler matches on conn_id_ to call set_idle_().
Resetting conn_id_ to UNSET_CONN_ID before CLOSE_EVT arrives
causes the event to be dropped, leaving the client stuck in
DISCONNECTING state permanently.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When OPEN_EVT arrives with a failure status, the connection was never
established so no CLOSE_EVT may follow. Reset conn_id_ to prevent a
stale value from matching a future CLOSE_EVT.
Instead of a separate early-return for DISCONNECTING state, let the
failed OPEN_EVT fall through to the existing failed-status handler
which already transitions to IDLE.
When a connection fails to establish, the ESP-IDF stack sends
DISCONNECT_EVT followed by OPEN_EVT with a failure status (e.g. 133).
No CLOSE_EVT follows since no GATT connection was established.
Previously the OPEN_EVT in DISCONNECTING state was silently ignored,
leaving the client stuck in DISCONNECTING forever. Now we treat this
failed OPEN_EVT as the terminal event and transition to IDLE.