diff --git a/esphome/components/sun/text_sensor/sun_text_sensor.h b/esphome/components/sun/text_sensor/sun_text_sensor.h index 5cb8fb001d..3474e35fc3 100644 --- a/esphome/components/sun/text_sensor/sun_text_sensor.h +++ b/esphome/components/sun/text_sensor/sun_text_sensor.h @@ -28,8 +28,8 @@ class SunTextSensor : public text_sensor::TextSensor, public PollingComponent { return; } - char buf[128]; - size_t len = res->strftime(buf, sizeof(buf), this->format_.c_str()); + char buf[ESPTime::STRFTIME_BUFFER_SIZE]; + size_t len = res->strftime_to(buf, this->format_.c_str()); if (len > 0) { this->publish_state(buf, len); } else { diff --git a/esphome/core/time.cpp b/esphome/core/time.cpp index d30dac4394..98dd77c61f 100644 --- a/esphome/core/time.cpp +++ b/esphome/core/time.cpp @@ -17,6 +17,11 @@ size_t ESPTime::strftime(char *buffer, size_t buffer_len, const char *format) { return ::strftime(buffer, buffer_len, format, &c_tm); } +size_t ESPTime::strftime_to(std::span buffer, const char *format) { + struct tm c_tm = this->to_c_tm(); + return ::strftime(buffer.data(), buffer.size(), format, &c_tm); +} + ESPTime ESPTime::from_c_tm(struct tm *c_tm, time_t c_time) { ESPTime res{}; res.second = uint8_t(c_tm->tm_sec); @@ -47,9 +52,8 @@ struct tm ESPTime::to_c_tm() { } std::string ESPTime::strftime(const char *format) { - struct tm c_tm = this->to_c_tm(); - char buf[128]; - size_t len = ::strftime(buf, sizeof(buf), format, &c_tm); + char buf[STRFTIME_BUFFER_SIZE]; + size_t len = this->strftime_to(buf, format); if (len > 0) { return std::string(buf, len); } diff --git a/esphome/core/time.h b/esphome/core/time.h index 68826dabdc..93bae194c3 100644 --- a/esphome/core/time.h +++ b/esphome/core/time.h @@ -3,6 +3,7 @@ #include #include #include +#include #include namespace esphome { @@ -13,6 +14,9 @@ uint8_t days_in_month(uint8_t month, uint16_t year); /// A more user-friendly version of struct tm from time.h struct ESPTime { + /// Buffer size required for strftime output + static constexpr size_t STRFTIME_BUFFER_SIZE = 128; + /** seconds after the minute [0-60] * @note second is generally 0-59; the extra range is to accommodate leap seconds. */ @@ -43,14 +47,21 @@ struct ESPTime { */ size_t strftime(char *buffer, size_t buffer_len, const char *format); + /** Format time into a fixed-size buffer, returns length written (0 on error). + * + * This is the preferred method for avoiding heap allocations. The buffer size is enforced at compile-time. + * @see https://www.gnu.org/software/libc/manual/html_node/Formatting-Calendar-Time.html#index-strftime + */ + size_t strftime_to(std::span buffer, const char *format); + /** Convert this ESPTime struct to a string as specified by the format argument. * @see https://en.cppreference.com/w/c/chrono/strftime * * @warning This method returns a dynamically allocated string which can cause heap fragmentation with some - * microcontrollers. + * microcontrollers. Prefer strftime_to() for heap-free formatting. * * @warning This method can return "ERROR" when the underlying strftime() call fails or when the - * output exceeds 128 bytes. + * output exceeds STRFTIME_BUFFER_SIZE bytes. */ std::string strftime(const std::string &format);