mirror of
https://github.com/esphome/esphome.git
synced 2026-06-02 03:02:19 +08:00
[time] Fix strftime %Z and %z returning wrong timezone (#15330)
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "posix_tz.h"
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
|
||||
namespace esphome::time {
|
||||
|
||||
@@ -442,6 +443,18 @@ bool parse_posix_tz(const char *tz_string, ParsedTimezone &result) {
|
||||
return internal::parse_dst_rule(p, result.dst_end);
|
||||
}
|
||||
|
||||
// Format a POSIX offset (positive = west) as "+HHMM" / "-HHMM" for display.
|
||||
// Convention: negate POSIX sign so east-of-UTC is positive (ISO 8601 / RFC 2822).
|
||||
void format_designation(int32_t posix_offset, char *buf, size_t buf_size) {
|
||||
int32_t display = -posix_offset;
|
||||
char sign = display >= 0 ? '+' : '-';
|
||||
if (display < 0)
|
||||
display = -display;
|
||||
int h = display / 3600;
|
||||
int m = (display % 3600) / 60;
|
||||
snprintf(buf, buf_size, "%c%02d%02d", sign, h, m);
|
||||
}
|
||||
|
||||
bool epoch_to_local_tm(time_t utc_epoch, const ParsedTimezone &tz, struct tm *out_tm) {
|
||||
if (!out_tm) {
|
||||
return false;
|
||||
|
||||
@@ -36,6 +36,9 @@ struct ParsedTimezone {
|
||||
bool has_dst() const { return this->dst_start.type != DSTRuleType::NONE; }
|
||||
};
|
||||
|
||||
/// Format a POSIX offset as "+HHMM"/"-HHMM" into buf (must be >= 6 bytes).
|
||||
void format_designation(int32_t posix_offset, char *buf, size_t buf_size);
|
||||
|
||||
/// Parse a POSIX TZ string into a ParsedTimezone struct.
|
||||
///
|
||||
/// @deprecated Remove before 2026.9.0 (bridge code for backward compatibility).
|
||||
|
||||
+52
-2
@@ -2,6 +2,9 @@
|
||||
#include "helpers.h"
|
||||
|
||||
#include <algorithm>
|
||||
#ifdef USE_TIME_TIMEZONE
|
||||
#include "esphome/components/time/posix_tz.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
|
||||
@@ -14,12 +17,59 @@ uint8_t days_in_month(uint8_t month, uint16_t year) {
|
||||
|
||||
size_t ESPTime::strftime(char *buffer, size_t buffer_len, const char *format) {
|
||||
struct tm c_tm = this->to_c_tm();
|
||||
#ifdef USE_TIME_TIMEZONE
|
||||
// ::strftime uses libc's internal timezone state for %Z and %z, but we
|
||||
// eliminated setenv("TZ")/tzset() on embedded platforms to save flash.
|
||||
// Substitute %Z and %z with correct values from our parsed timezone.
|
||||
// Quick scan: does format contain %Z or %z (but not %%Z/%%z)?
|
||||
bool needs_subst = false;
|
||||
for (const char *p = format; *p; p++) {
|
||||
if (*p == '%' && *(p + 1)) {
|
||||
p++;
|
||||
if (*p == '%')
|
||||
continue; // %% is a literal %, skip
|
||||
if (*p == 'Z' || *p == 'z') {
|
||||
needs_subst = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (needs_subst) {
|
||||
const auto &tz = time::get_global_tz();
|
||||
char designation[6]; // "+HHMM" + null
|
||||
int32_t offset = c_tm.tm_isdst > 0 ? tz.dst_offset_seconds : tz.std_offset_seconds;
|
||||
time::format_designation(offset, designation, sizeof(designation));
|
||||
|
||||
char modified[STRFTIME_BUFFER_SIZE];
|
||||
char *out = modified;
|
||||
char *out_end = modified + sizeof(modified) - 1;
|
||||
for (const char *p = format; *p && out < out_end; p++) {
|
||||
if (*p == '%') {
|
||||
if (*(p + 1) == '%') {
|
||||
// %% → copy both percent signs (literal %)
|
||||
*out++ = *p++;
|
||||
if (out < out_end)
|
||||
*out++ = *p;
|
||||
} else if (*(p + 1) == 'Z' || *(p + 1) == 'z') {
|
||||
p++; // skip the Z/z
|
||||
for (const char *d = designation; *d && out < out_end; d++)
|
||||
*out++ = *d;
|
||||
} else {
|
||||
*out++ = *p;
|
||||
}
|
||||
} else {
|
||||
*out++ = *p;
|
||||
}
|
||||
}
|
||||
*out = '\0';
|
||||
return ::strftime(buffer, buffer_len, modified, &c_tm);
|
||||
}
|
||||
#endif
|
||||
return ::strftime(buffer, buffer_len, format, &c_tm);
|
||||
}
|
||||
|
||||
size_t ESPTime::strftime_to(std::span<char, STRFTIME_BUFFER_SIZE> buffer, const char *format) {
|
||||
struct tm c_tm = this->to_c_tm();
|
||||
size_t len = ::strftime(buffer.data(), buffer.size(), format, &c_tm);
|
||||
size_t len = this->strftime(buffer.data(), buffer.size(), format);
|
||||
if (len > 0) {
|
||||
return len;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user