ESPHome 2026.3.0-dev
Loading...
Searching...
No Matches
real_time_clock.cpp
Go to the documentation of this file.
1#include "real_time_clock.h"
2#include "esphome/core/log.h"
3#ifdef USE_HOST
4#include <sys/time.h>
5#elif defined(USE_ZEPHYR)
6#include <zephyr/posix/time.h>
7#else
8#include "lwip/opt.h"
9#endif
10#ifdef USE_ESP8266
11#include "sys/time.h"
12#endif
13#if defined(USE_RP2040) || defined(USE_ZEPHYR)
14#include <sys/time.h>
15#endif
16#include <cerrno>
17#include <cinttypes>
18#include <cstdlib>
19
20namespace esphome::time {
21
22static const char *const TAG = "time";
23
25
27#ifdef USE_TIME_TIMEZONE
28 time_t epoch = this->timestamp_now();
29 struct tm local_tm;
30 if (epoch_to_local_tm(epoch, get_global_tz(), &local_tm)) {
31 return ESPTime::from_c_tm(&local_tm, epoch);
32 }
33 // Fallback to UTC if parsing failed
34 return ESPTime::from_epoch_utc(epoch);
35#else
37#endif
38}
39
41#ifdef USE_TIME_TIMEZONE
42 const auto &tz = get_global_tz();
43 // POSIX offset is positive west, negate for conventional UTC+X display
44 int std_h = -tz.std_offset_seconds / 3600;
45 int std_m = (std::abs(tz.std_offset_seconds) % 3600) / 60;
46 if (tz.has_dst()) {
47 int dst_h = -tz.dst_offset_seconds / 3600;
48 int dst_m = (std::abs(tz.dst_offset_seconds) % 3600) / 60;
49 ESP_LOGCONFIG(TAG, "Timezone: UTC%+d:%02d (DST UTC%+d:%02d)", std_h, std_m, dst_h, dst_m);
50 } else {
51 ESP_LOGCONFIG(TAG, "Timezone: UTC%+d:%02d", std_h, std_m);
52 }
53#endif
54 auto time = this->now();
55 ESP_LOGCONFIG(TAG, "Current time: %04d-%02d-%02d %02d:%02d:%02d", time.year, time.month, time.day_of_month, time.hour,
56 time.minute, time.second);
57}
58
60 ESP_LOGVV(TAG, "Got epoch %" PRIu32, epoch);
61 // Skip if time is already synchronized to avoid unnecessary writes, log spam,
62 // and prevent clock jumping backwards due to network latency
63 constexpr time_t min_valid_epoch = 1546300800; // January 1, 2019
64 time_t current_time = this->timestamp_now();
65 // Check if time is valid (year >= 2019) before comparing
66 if (current_time >= min_valid_epoch) {
67 // Unsigned subtraction handles wraparound correctly, then cast to signed
68 int32_t diff = static_cast<int32_t>(epoch - static_cast<uint32_t>(current_time));
69 if (diff >= -1 && diff <= 1) {
70 // Time is already synchronized, but still call callbacks so components
71 // waiting for time sync (e.g., uptime timestamp sensor) can initialize
72 this->time_sync_callback_.call();
73 return;
74 }
75 }
76 // Update UTC epoch time.
77#ifdef USE_ZEPHYR
78 struct timespec ts;
79 ts.tv_nsec = 0;
80 ts.tv_sec = static_cast<time_t>(epoch);
81
82 int ret = clock_settime(CLOCK_REALTIME, &ts);
83
84 if (ret != 0) {
85 ESP_LOGW(TAG, "clock_settime() failed with code %d", ret);
86 }
87#else
88 struct timeval timev {
89 .tv_sec = static_cast<time_t>(epoch), .tv_usec = 0,
90 };
91 struct timezone tz = {0, 0};
92 int ret = settimeofday(&timev, &tz);
93 if (ret == EINVAL) {
94 // Some ESP8266 frameworks abort when timezone parameter is not NULL
95 // while ESP32 expects it not to be NULL
96 ret = settimeofday(&timev, nullptr);
97 }
98
99 if (ret != 0) {
100 ESP_LOGW(TAG, "setimeofday() failed with code %d", ret);
101 }
102#endif
103 auto time = this->now();
104 ESP_LOGD(TAG, "Synchronized time: %04d-%02d-%02d %02d:%02d:%02d", time.year, time.month, time.day_of_month, time.hour,
105 time.minute, time.second);
106
107 this->time_sync_callback_.call();
108}
109
110#ifdef USE_TIME_TIMEZONE
112 ParsedTimezone parsed{};
113
114 // Handle null or empty input - use UTC
115 if (tz == nullptr || *tz == '\0') {
116 // Skip if already UTC
117 if (!get_global_tz().has_dst() && get_global_tz().std_offset_seconds == 0) {
118 return;
119 }
120 set_global_tz(parsed);
121 return;
122 }
123
124#ifdef USE_HOST
125 // On host platform, also set TZ environment variable for libc compatibility
126 setenv("TZ", tz, 1);
127 tzset();
128#endif
129
130 // Parse the POSIX TZ string using our custom parser
131 if (!parse_posix_tz(tz, parsed)) {
132 ESP_LOGW(TAG, "Failed to parse timezone: %s", tz);
133 return;
134 }
135
136 // Set global timezone for all time conversions
137 set_global_tz(parsed);
138}
139#endif
140
141} // namespace esphome::time
time_t timestamp_now()
Get the current time as the UTC epoch since January 1st 1970.
ESPTime now()
Get the time in the currently defined timezone.
void apply_timezone_(const char *tz)
LazyCallbackManager< void()> time_sync_callback_
void synchronize_epoch_(uint32_t epoch)
Report a unix epoch as current time.
const char *const TAG
Definition spi.cpp:7
void set_global_tz(const ParsedTimezone &tz)
Set the global timezone used by epoch_to_local_tm() when called without a timezone.
Definition posix_tz.cpp:14
const ParsedTimezone & get_global_tz()
Get the global timezone.
Definition posix_tz.cpp:16
ESPTime __attribute__((noinline)) RealTimeClock
bool parse_posix_tz(const char *tz_string, ParsedTimezone &result)
Parse a POSIX TZ string into a ParsedTimezone struct.
Definition posix_tz.cpp:374
bool const ParsedTimezone & tz
Definition posix_tz.cpp:346
bool epoch_to_local_tm(time_t utc_epoch, const ParsedTimezone &tz, struct tm *out_tm)
Convert a UTC epoch to local time using the parsed timezone.
Definition posix_tz.cpp:445
A more user-friendly version of struct tm from time.h.
Definition time.h:21
static ESPTime from_epoch_local(time_t epoch)
Convert an UTC epoch timestamp to a local time ESPTime instance.
Definition time.h:111
static ESPTime from_c_tm(struct tm *c_tm, time_t c_time)
Convert a C tm struct instance with a C unix epoch timestamp to an ESPTime instance.
Definition time.cpp:32
static ESPTime from_epoch_utc(time_t epoch)
Convert an UTC epoch timestamp to a UTC time ESPTime instance.
Definition time.h:129
Parsed POSIX timezone information (packed for 32-bit: 32 bytes)
Definition posix_tz.h:29
bool has_dst() const
Check if this timezone has DST rules.
Definition posix_tz.h:36
int32_t dst_offset_seconds
DST offset from UTC in seconds.
Definition posix_tz.h:31
int32_t std_offset_seconds
Standard time offset from UTC in seconds (positive = west)
Definition posix_tz.h:30