From 302dea4169ce574bc3ff89b53dcdda724ce4ec3f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 9 Jun 2023 10:24:44 +1200 Subject: [PATCH] Move ESPTime into core esphome namespace (#4926) * Prep-work for datetime entities * Fix some includes and remove some restrictions on printing time on displays * format * format * More formatting * Move function contents * Ignore clang-tidy --- esphome/components/bedjet/bedjet_hub.cpp | 2 +- esphome/components/bedjet/bedjet_hub.h | 1 + .../deep_sleep/deep_sleep_component.h | 7 +- esphome/components/display/display_buffer.cpp | 13 +- esphome/components/display/display_buffer.h | 24 +-- esphome/components/ds1307/ds1307.cpp | 16 +- esphome/components/gps/time/gps_time.cpp | 2 +- esphome/components/lcd_base/lcd_display.cpp | 6 +- esphome/components/lcd_base/lcd_display.h | 13 +- esphome/components/max7219/max7219.cpp | 6 +- esphome/components/max7219/max7219.h | 12 +- .../components/max7219digit/max7219digit.cpp | 6 +- .../components/max7219digit/max7219digit.h | 13 +- esphome/components/nextion/nextion.h | 10 +- .../components/nextion/nextion_commands.cpp | 4 +- esphome/components/pcf85063/pcf85063.cpp | 2 +- esphome/components/sun/sun.cpp | 36 ++-- esphome/components/sun/sun.h | 18 +- .../sun/text_sensor/sun_text_sensor.h | 4 +- esphome/components/time/automation.cpp | 2 + esphome/components/time/automation.h | 4 +- esphome/components/time/real_time_clock.cpp | 166 ----------------- esphome/components/time/real_time_clock.h | 97 +--------- esphome/components/tm1637/tm1637.cpp | 6 +- esphome/components/tm1637/tm1637.h | 11 +- esphome/components/tm1638/tm1638.cpp | 6 +- esphome/components/tm1638/tm1638.h | 13 +- esphome/components/tuya/tuya.cpp | 2 +- esphome/components/tuya/tuya.h | 1 + esphome/core/time.cpp | 171 ++++++++++++++++++ esphome/core/time.h | 105 +++++++++++ 31 files changed, 376 insertions(+), 403 deletions(-) create mode 100644 esphome/core/time.cpp create mode 100644 esphome/core/time.h diff --git a/esphome/components/bedjet/bedjet_hub.cpp b/esphome/components/bedjet/bedjet_hub.cpp index fbd2876dc9..c355953d94 100644 --- a/esphome/components/bedjet/bedjet_hub.cpp +++ b/esphome/components/bedjet/bedjet_hub.cpp @@ -442,7 +442,7 @@ uint8_t BedJetHub::write_notify_config_descriptor_(bool enable) { void BedJetHub::send_local_time() { if (this->time_id_.has_value()) { auto *time_id = *this->time_id_; - time::ESPTime now = time_id->now(); + ESPTime now = time_id->now(); if (now.is_valid()) { this->set_clock(now.hour, now.minute); ESP_LOGD(TAG, "Using time component to set BedJet clock: %d:%02d", now.hour, now.minute); diff --git a/esphome/components/bedjet/bedjet_hub.h b/esphome/components/bedjet/bedjet_hub.h index 5809827cfa..bb1349b2ac 100644 --- a/esphome/components/bedjet/bedjet_hub.h +++ b/esphome/components/bedjet/bedjet_hub.h @@ -13,6 +13,7 @@ #ifdef USE_TIME #include "esphome/components/time/real_time_clock.h" +#include "esphome/core/time.h" #endif #include diff --git a/esphome/components/deep_sleep/deep_sleep_component.h b/esphome/components/deep_sleep/deep_sleep_component.h index 8dc87cece8..2e54e53c56 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.h +++ b/esphome/components/deep_sleep/deep_sleep_component.h @@ -1,9 +1,9 @@ #pragma once -#include "esphome/core/component.h" -#include "esphome/core/helpers.h" #include "esphome/core/automation.h" +#include "esphome/core/component.h" #include "esphome/core/hal.h" +#include "esphome/core/helpers.h" #ifdef USE_ESP32 #include @@ -11,6 +11,7 @@ #ifdef USE_TIME #include "esphome/components/time/real_time_clock.h" +#include "esphome/core/time.h" #endif namespace esphome { @@ -170,7 +171,7 @@ template class EnterDeepSleepAction : public Action { if (after_time) timestamp += 60 * 60 * 24; - int32_t offset = time::ESPTime::timezone_offset(); + int32_t offset = ESPTime::timezone_offset(); timestamp -= offset; // Change timestamp to utc const uint32_t ms_left = (timestamp - timestamp_now) * 1000; this->deep_sleep_->set_sleep_duration(ms_left); diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index 0d76fa09ec..1be2950d68 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -3,9 +3,9 @@ #include #include "esphome/core/application.h" #include "esphome/core/color.h" -#include "esphome/core/log.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" +#include "esphome/core/log.h" namespace esphome { namespace display { @@ -490,24 +490,21 @@ void DisplayOnPageChangeTrigger::process(DisplayPage *from, DisplayPage *to) { if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to)) this->trigger(from, to); } -#ifdef USE_TIME -void DisplayBuffer::strftime(int x, int y, Font *font, Color color, TextAlign align, const char *format, - time::ESPTime time) { +void DisplayBuffer::strftime(int x, int y, Font *font, Color color, TextAlign align, const char *format, ESPTime time) { char buffer[64]; size_t ret = time.strftime(buffer, sizeof(buffer), format); if (ret > 0) this->print(x, y, font, color, align, buffer); } -void DisplayBuffer::strftime(int x, int y, Font *font, Color color, const char *format, time::ESPTime time) { +void DisplayBuffer::strftime(int x, int y, Font *font, Color color, const char *format, ESPTime time) { this->strftime(x, y, font, color, TextAlign::TOP_LEFT, format, time); } -void DisplayBuffer::strftime(int x, int y, Font *font, TextAlign align, const char *format, time::ESPTime time) { +void DisplayBuffer::strftime(int x, int y, Font *font, TextAlign align, const char *format, ESPTime time) { this->strftime(x, y, font, COLOR_ON, align, format, time); } -void DisplayBuffer::strftime(int x, int y, Font *font, const char *format, time::ESPTime time) { +void DisplayBuffer::strftime(int x, int y, Font *font, const char *format, ESPTime time) { this->strftime(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, time); } -#endif void DisplayBuffer::start_clipping(Rect rect) { if (!this->clipping_rectangle_.empty()) { diff --git a/esphome/components/display/display_buffer.h b/esphome/components/display/display_buffer.h index 2474d6f5a0..bcfe75f6b4 100644 --- a/esphome/components/display/display_buffer.h +++ b/esphome/components/display/display_buffer.h @@ -1,15 +1,12 @@ #pragma once -#include "esphome/core/component.h" -#include "esphome/core/defines.h" -#include "esphome/core/automation.h" -#include "display_color_utils.h" #include #include - -#ifdef USE_TIME -#include "esphome/components/time/real_time_clock.h" -#endif +#include "display_color_utils.h" +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/core/defines.h" +#include "esphome/core/time.h" #ifdef USE_GRAPH #include "esphome/components/graph/graph.h" @@ -263,7 +260,6 @@ class DisplayBuffer { */ void printf(int x, int y, Font *font, const char *format, ...) __attribute__((format(printf, 5, 6))); -#ifdef USE_TIME /** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`. * * @param x The x coordinate of the text alignment anchor point. @@ -274,7 +270,7 @@ class DisplayBuffer { * @param format The strftime format to use. * @param time The time to format. */ - void strftime(int x, int y, Font *font, Color color, TextAlign align, const char *format, time::ESPTime time) + void strftime(int x, int y, Font *font, Color color, TextAlign align, const char *format, ESPTime time) __attribute__((format(strftime, 7, 0))); /** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`. @@ -286,7 +282,7 @@ class DisplayBuffer { * @param format The strftime format to use. * @param time The time to format. */ - void strftime(int x, int y, Font *font, Color color, const char *format, time::ESPTime time) + void strftime(int x, int y, Font *font, Color color, const char *format, ESPTime time) __attribute__((format(strftime, 6, 0))); /** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`. @@ -298,7 +294,7 @@ class DisplayBuffer { * @param format The strftime format to use. * @param time The time to format. */ - void strftime(int x, int y, Font *font, TextAlign align, const char *format, time::ESPTime time) + void strftime(int x, int y, Font *font, TextAlign align, const char *format, ESPTime time) __attribute__((format(strftime, 6, 0))); /** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`. @@ -309,9 +305,7 @@ class DisplayBuffer { * @param format The strftime format to use. * @param time The time to format. */ - void strftime(int x, int y, Font *font, const char *format, time::ESPTime time) - __attribute__((format(strftime, 5, 0))); -#endif + void strftime(int x, int y, Font *font, const char *format, ESPTime time) __attribute__((format(strftime, 5, 0))); /** Draw the `image` with the top-left corner at [x,y] to the screen. * diff --git a/esphome/components/ds1307/ds1307.cpp b/esphome/components/ds1307/ds1307.cpp index d249e9743a..472ccc7a9a 100644 --- a/esphome/components/ds1307/ds1307.cpp +++ b/esphome/components/ds1307/ds1307.cpp @@ -37,14 +37,14 @@ void DS1307Component::read_time() { ESP_LOGW(TAG, "RTC halted, not syncing to system clock."); return; } - time::ESPTime rtc_time{.second = uint8_t(ds1307_.reg.second + 10 * ds1307_.reg.second_10), - .minute = uint8_t(ds1307_.reg.minute + 10u * ds1307_.reg.minute_10), - .hour = uint8_t(ds1307_.reg.hour + 10u * ds1307_.reg.hour_10), - .day_of_week = uint8_t(ds1307_.reg.weekday), - .day_of_month = uint8_t(ds1307_.reg.day + 10u * ds1307_.reg.day_10), - .day_of_year = 1, // ignored by recalc_timestamp_utc(false) - .month = uint8_t(ds1307_.reg.month + 10u * ds1307_.reg.month_10), - .year = uint16_t(ds1307_.reg.year + 10u * ds1307_.reg.year_10 + 2000)}; + ESPTime rtc_time{.second = uint8_t(ds1307_.reg.second + 10 * ds1307_.reg.second_10), + .minute = uint8_t(ds1307_.reg.minute + 10u * ds1307_.reg.minute_10), + .hour = uint8_t(ds1307_.reg.hour + 10u * ds1307_.reg.hour_10), + .day_of_week = uint8_t(ds1307_.reg.weekday), + .day_of_month = uint8_t(ds1307_.reg.day + 10u * ds1307_.reg.day_10), + .day_of_year = 1, // ignored by recalc_timestamp_utc(false) + .month = uint8_t(ds1307_.reg.month + 10u * ds1307_.reg.month_10), + .year = uint16_t(ds1307_.reg.year + 10u * ds1307_.reg.year_10 + 2000)}; rtc_time.recalc_timestamp_utc(false); if (!rtc_time.is_valid()) { ESP_LOGE(TAG, "Invalid RTC time, not syncing to system clock."); diff --git a/esphome/components/gps/time/gps_time.cpp b/esphome/components/gps/time/gps_time.cpp index e46f24ba8e..0f1b989f77 100644 --- a/esphome/components/gps/time/gps_time.cpp +++ b/esphome/components/gps/time/gps_time.cpp @@ -16,7 +16,7 @@ void GPSTime::from_tiny_gps_(TinyGPSPlus &tiny_gps) { if (tiny_gps.date.year() < 2019) return; - time::ESPTime val{}; + ESPTime val{}; val.year = tiny_gps.date.year(); val.month = tiny_gps.date.month(); val.day_of_month = tiny_gps.date.day(); diff --git a/esphome/components/lcd_base/lcd_display.cpp b/esphome/components/lcd_base/lcd_display.cpp index 180d5e93ac..65d7aa508f 100644 --- a/esphome/components/lcd_base/lcd_display.cpp +++ b/esphome/components/lcd_base/lcd_display.cpp @@ -158,15 +158,13 @@ void LCDDisplay::clear() { for (uint8_t i = 0; i < this->rows_ * this->columns_; i++) this->buffer_[i] = ' '; } -#ifdef USE_TIME -void LCDDisplay::strftime(uint8_t column, uint8_t row, const char *format, time::ESPTime time) { +void LCDDisplay::strftime(uint8_t column, uint8_t row, const char *format, ESPTime time) { char buffer[64]; size_t ret = time.strftime(buffer, sizeof(buffer), format); if (ret > 0) this->print(column, row, buffer); } -void LCDDisplay::strftime(const char *format, time::ESPTime time) { this->strftime(0, 0, format, time); } -#endif +void LCDDisplay::strftime(const char *format, ESPTime time) { this->strftime(0, 0, format, time); } void LCDDisplay::loadchar(uint8_t location, uint8_t charmap[]) { location &= 0x7; // we only have 8 locations 0-7 this->command_(LCD_DISPLAY_COMMAND_SET_CGRAM_ADDR | (location << 3)); diff --git a/esphome/components/lcd_base/lcd_display.h b/esphome/components/lcd_base/lcd_display.h index c1dc54a9ed..473acb0bd3 100644 --- a/esphome/components/lcd_base/lcd_display.h +++ b/esphome/components/lcd_base/lcd_display.h @@ -1,11 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/defines.h" - -#ifdef USE_TIME -#include "esphome/components/time/real_time_clock.h" -#endif +#include "esphome/core/time.h" #include #include @@ -44,13 +40,10 @@ class LCDDisplay : public PollingComponent { /// Evaluate the printf-format and print the text at column=0 and row=0. void printf(const char *format, ...) __attribute__((format(printf, 2, 3))); -#ifdef USE_TIME /// Evaluate the strftime-format and print the text at the specified column and row. - void strftime(uint8_t column, uint8_t row, const char *format, time::ESPTime time) - __attribute__((format(strftime, 4, 0))); + void strftime(uint8_t column, uint8_t row, const char *format, ESPTime time) __attribute__((format(strftime, 4, 0))); /// Evaluate the strftime-format and print the text at column=0 and row=0. - void strftime(const char *format, time::ESPTime time) __attribute__((format(strftime, 2, 0))); -#endif + void strftime(const char *format, ESPTime time) __attribute__((format(strftime, 2, 0))); /// Load custom char to given location void loadchar(uint8_t location, uint8_t charmap[]); diff --git a/esphome/components/max7219/max7219.cpp b/esphome/components/max7219/max7219.cpp index b50a78eb96..38b4a165cb 100644 --- a/esphome/components/max7219/max7219.cpp +++ b/esphome/components/max7219/max7219.cpp @@ -223,16 +223,14 @@ void MAX7219Component::set_intensity(uint8_t intensity) { } void MAX7219Component::set_num_chips(uint8_t num_chips) { this->num_chips_ = num_chips; } -#ifdef USE_TIME -uint8_t MAX7219Component::strftime(uint8_t pos, const char *format, time::ESPTime time) { +uint8_t MAX7219Component::strftime(uint8_t pos, const char *format, ESPTime time) { char buffer[64]; size_t ret = time.strftime(buffer, sizeof(buffer), format); if (ret > 0) return this->print(pos, buffer); return 0; } -uint8_t MAX7219Component::strftime(const char *format, time::ESPTime time) { return this->strftime(0, format, time); } -#endif +uint8_t MAX7219Component::strftime(const char *format, ESPTime time) { return this->strftime(0, format, time); } } // namespace max7219 } // namespace esphome diff --git a/esphome/components/max7219/max7219.h b/esphome/components/max7219/max7219.h index 47b54a4c50..1b724cef69 100644 --- a/esphome/components/max7219/max7219.h +++ b/esphome/components/max7219/max7219.h @@ -1,11 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/defines.h" - -#ifdef USE_TIME -#include "esphome/components/time/real_time_clock.h" -#endif +#include "esphome/core/time.h" #include "esphome/components/spi/spi.h" @@ -46,13 +42,11 @@ class MAX7219Component : public PollingComponent, /// Print `str` at position 0. uint8_t print(const char *str); -#ifdef USE_TIME /// Evaluate the strftime-format and print the result at the given position. - uint8_t strftime(uint8_t pos, const char *format, time::ESPTime time) __attribute__((format(strftime, 3, 0))); + uint8_t strftime(uint8_t pos, const char *format, ESPTime time) __attribute__((format(strftime, 3, 0))); /// Evaluate the strftime-format and print the result at position 0. - uint8_t strftime(const char *format, time::ESPTime time) __attribute__((format(strftime, 2, 0))); -#endif + uint8_t strftime(const char *format, ESPTime time) __attribute__((format(strftime, 2, 0))); protected: void send_byte_(uint8_t a_register, uint8_t data); diff --git a/esphome/components/max7219digit/max7219digit.cpp b/esphome/components/max7219digit/max7219digit.cpp index c65b8e4823..7d21032b8a 100644 --- a/esphome/components/max7219digit/max7219digit.cpp +++ b/esphome/components/max7219digit/max7219digit.cpp @@ -325,18 +325,16 @@ uint8_t MAX7219Component::printdigitf(const char *format, ...) { return 0; } -#ifdef USE_TIME -uint8_t MAX7219Component::strftimedigit(uint8_t pos, const char *format, time::ESPTime time) { +uint8_t MAX7219Component::strftimedigit(uint8_t pos, const char *format, ESPTime time) { char buffer[64]; size_t ret = time.strftime(buffer, sizeof(buffer), format); if (ret > 0) return this->printdigit(pos, buffer); return 0; } -uint8_t MAX7219Component::strftimedigit(const char *format, time::ESPTime time) { +uint8_t MAX7219Component::strftimedigit(const char *format, ESPTime time) { return this->strftimedigit(0, format, time); } -#endif } // namespace max7219digit } // namespace esphome diff --git a/esphome/components/max7219digit/max7219digit.h b/esphome/components/max7219digit/max7219digit.h index 17e369a9d9..93d2af21f9 100644 --- a/esphome/components/max7219digit/max7219digit.h +++ b/esphome/components/max7219digit/max7219digit.h @@ -1,16 +1,13 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/defines.h" +#include "esphome/core/time.h" + #include "esphome/components/display/display_buffer.h" #include "esphome/components/spi/spi.h" #include -#ifdef USE_TIME -#include "esphome/components/time/real_time_clock.h" -#endif - namespace esphome { namespace max7219digit { @@ -88,13 +85,11 @@ class MAX7219Component : public PollingComponent, /// Print `str` at position 0. uint8_t printdigit(const char *str); -#ifdef USE_TIME /// Evaluate the strftime-format and print the result at the given position. - uint8_t strftimedigit(uint8_t pos, const char *format, time::ESPTime time) __attribute__((format(strftime, 3, 0))); + uint8_t strftimedigit(uint8_t pos, const char *format, ESPTime time) __attribute__((format(strftime, 3, 0))); /// Evaluate the strftime-format and print the result at position 0. - uint8_t strftimedigit(const char *format, time::ESPTime time) __attribute__((format(strftime, 2, 0))); -#endif + uint8_t strftimedigit(const char *format, ESPTime time) __attribute__((format(strftime, 2, 0))); display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_BINARY; } diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index 86610ef564..28663138d7 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -4,6 +4,8 @@ #include #include "esphome/core/defines.h" +#include "esphome/core/time.h" + #include "esphome/components/uart/uart.h" #include "nextion_base.h" #include "nextion_component.h" @@ -19,10 +21,6 @@ #endif #endif -#ifdef USE_TIME -#include "esphome/components/time/real_time_clock.h" -#endif - namespace esphome { namespace nextion { @@ -318,13 +316,11 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * Changes the font of the component named `textveiw`. Font IDs are set in the Nextion Editor. */ void set_component_font(const char *component, uint8_t font_id) override; -#ifdef USE_TIME /** * Send the current time to the nextion display. * @param time The time instance to send (get this with id(my_time).now() ). */ - void set_nextion_rtc_time(time::ESPTime time); -#endif + void set_nextion_rtc_time(ESPTime time); /** * Show the page with a given name. diff --git a/esphome/components/nextion/nextion_commands.cpp b/esphome/components/nextion/nextion_commands.cpp index 308e02bce8..0409e5ea6c 100644 --- a/esphome/components/nextion/nextion_commands.cpp +++ b/esphome/components/nextion/nextion_commands.cpp @@ -219,8 +219,7 @@ void Nextion::filled_circle(int center_x, int center_y, int radius, Color color) display::ColorUtil::color_to_565(color)); } -#ifdef USE_TIME -void Nextion::set_nextion_rtc_time(time::ESPTime time) { +void Nextion::set_nextion_rtc_time(ESPTime time) { this->add_no_result_to_queue_with_printf_("rtc0", "rtc0=%u", time.year); this->add_no_result_to_queue_with_printf_("rtc1", "rtc1=%u", time.month); this->add_no_result_to_queue_with_printf_("rtc2", "rtc2=%u", time.day_of_month); @@ -228,7 +227,6 @@ void Nextion::set_nextion_rtc_time(time::ESPTime time) { this->add_no_result_to_queue_with_printf_("rtc4", "rtc4=%u", time.minute); this->add_no_result_to_queue_with_printf_("rtc5", "rtc5=%u", time.second); } -#endif } // namespace nextion } // namespace esphome diff --git a/esphome/components/pcf85063/pcf85063.cpp b/esphome/components/pcf85063/pcf85063.cpp index 5073522655..debc007cb8 100644 --- a/esphome/components/pcf85063/pcf85063.cpp +++ b/esphome/components/pcf85063/pcf85063.cpp @@ -37,7 +37,7 @@ void PCF85063Component::read_time() { ESP_LOGW(TAG, "RTC halted, not syncing to system clock."); return; } - time::ESPTime rtc_time{ + ESPTime rtc_time{ .second = uint8_t(pcf85063_.reg.second + 10 * pcf85063_.reg.second_10), .minute = uint8_t(pcf85063_.reg.minute + 10u * pcf85063_.reg.minute_10), .hour = uint8_t(pcf85063_.reg.hour + 10u * pcf85063_.reg.hour_10), diff --git a/esphome/components/sun/sun.cpp b/esphome/components/sun/sun.cpp index 5f9179682a..2fd9394a5e 100644 --- a/esphome/components/sun/sun.cpp +++ b/esphome/components/sun/sun.cpp @@ -37,7 +37,7 @@ num_t EquatorialCoordinate::declination_rad() const { return radians(declination num_t HorizontalCoordinate::elevation_rad() const { return radians(elevation); } num_t HorizontalCoordinate::azimuth_rad() const { return radians(azimuth); } -num_t julian_day(time::ESPTime moment) { +num_t julian_day(ESPTime moment) { // p. 59 // UT -> JD, TT -> JDE int y = moment.year; @@ -54,7 +54,7 @@ num_t julian_day(time::ESPTime moment) { int b = 2 - a + a / 4; return ((int) (365.25 * (y + 4716))) + ((int) (30.6001 * (m + 1))) + d + b - 1524.5; } -num_t delta_t(time::ESPTime moment) { +num_t delta_t(ESPTime moment) { // approximation for 2005-2050 from NASA (https://eclipse.gsfc.nasa.gov/SEhelp/deltatpoly2004.html) int t = moment.year - 2000; return 62.92 + t * (0.32217 + t * 0.005589); @@ -199,7 +199,7 @@ struct SunAtLocation { // see chapter 12, p. 87 num_t jd = moment.jd(); // eq 12.1, p.87; jd for 0h UT of this date - time::ESPTime moment_0h = moment.dt; + ESPTime moment_0h = moment.dt; moment_0h.hour = moment_0h.minute = moment_0h.second = 0; num_t jd0 = Moment{moment_0h}.jd(); num_t t = (jd0 - 2451545) / 36525; @@ -227,9 +227,9 @@ struct SunAtLocation { return HorizontalCoordinate{degrees(elevation_rad), degrees(azimuth_rad) + 180}; } - optional sunrise(time::ESPTime date, num_t zenith) const { return event(true, date, zenith); } - optional sunset(time::ESPTime date, num_t zenith) const { return event(false, date, zenith); } - optional event(bool rise, time::ESPTime date, num_t zenith) const { + optional sunrise(ESPTime date, num_t zenith) const { return event(true, date, zenith); } + optional sunset(ESPTime date, num_t zenith) const { return event(false, date, zenith); } + optional event(bool rise, ESPTime date, num_t zenith) const { // couldn't get the method described in chapter 15 to work, // so instead this is based on the algorithm in time4j // https://github.com/MenoData/Time4J/blob/master/base/src/main/java/net/time4j/calendar/astro/StdSolarCalculator.java @@ -244,7 +244,7 @@ struct SunAtLocation { new_h = *x; } while (std::abs(new_h - old_h) >= 15); time_t new_timestamp = m.timestamp + (time_t) new_h; - return time::ESPTime::from_epoch_local(new_timestamp); + return ESPTime::from_epoch_local(new_timestamp); } protected: @@ -263,14 +263,14 @@ struct SunAtLocation { return hour_angle; } - time::ESPTime local_event_(time::ESPTime date, int hour) const { + ESPTime local_event_(ESPTime date, int hour) const { // input date should be in UTC, and hour/minute/second fields 0 num_t added_d = hour / 24.0 - location.longitude / 360; num_t jd = julian_day(date) + added_d; num_t eot = SunAtTime(jd).equation_of_time() * 240; time_t new_timestamp = (time_t) (date.timestamp + added_d * 86400 - eot); - return time::ESPTime::from_epoch_utc(new_timestamp); + return ESPTime::from_epoch_utc(new_timestamp); } }; @@ -287,7 +287,7 @@ HorizontalCoordinate Sun::calc_coords_() { */ return sun.true_coordinate(m); } -optional Sun::calc_event_(time::ESPTime date, bool rising, double zenith) { +optional Sun::calc_event_(ESPTime date, bool rising, double zenith) { SunAtLocation sun{location_}; if (!date.is_valid()) return {}; @@ -301,24 +301,20 @@ optional Sun::calc_event_(time::ESPTime date, bool rising, double // We're calculating *next* sunrise/sunset, but calculated event // is today, so try again tomorrow time_t new_timestamp = today.timestamp + 24 * 60 * 60; - today = time::ESPTime::from_epoch_utc(new_timestamp); + today = ESPTime::from_epoch_utc(new_timestamp); it = sun.event(rising, today, zenith); } return it; } -optional Sun::calc_event_(bool rising, double zenith) { +optional Sun::calc_event_(bool rising, double zenith) { auto it = Sun::calc_event_(this->time_->utcnow(), rising, zenith); return it; } -optional Sun::sunrise(double elevation) { return this->calc_event_(true, 90 - elevation); } -optional Sun::sunset(double elevation) { return this->calc_event_(false, 90 - elevation); } -optional Sun::sunrise(time::ESPTime date, double elevation) { - return this->calc_event_(date, true, 90 - elevation); -} -optional Sun::sunset(time::ESPTime date, double elevation) { - return this->calc_event_(date, false, 90 - elevation); -} +optional Sun::sunrise(double elevation) { return this->calc_event_(true, 90 - elevation); } +optional Sun::sunset(double elevation) { return this->calc_event_(false, 90 - elevation); } +optional Sun::sunrise(ESPTime date, double elevation) { return this->calc_event_(date, true, 90 - elevation); } +optional Sun::sunset(ESPTime date, double elevation) { return this->calc_event_(date, false, 90 - elevation); } double Sun::elevation() { return this->calc_coords_().elevation; } double Sun::azimuth() { return this->calc_coords_().azimuth; } diff --git a/esphome/components/sun/sun.h b/esphome/components/sun/sun.h index 9547b2f280..de4801a655 100644 --- a/esphome/components/sun/sun.h +++ b/esphome/components/sun/sun.h @@ -1,8 +1,10 @@ #pragma once +#include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" -#include "esphome/core/automation.h" +#include "esphome/core/time.h" + #include "esphome/components/time/real_time_clock.h" namespace esphome { @@ -26,7 +28,7 @@ struct GeoLocation { }; struct Moment { - time::ESPTime dt; + ESPTime dt; num_t jd() const; num_t jde() const; @@ -57,18 +59,18 @@ class Sun { void set_latitude(double latitude) { location_.latitude = latitude; } void set_longitude(double longitude) { location_.longitude = longitude; } - optional sunrise(double elevation); - optional sunset(double elevation); - optional sunrise(time::ESPTime date, double elevation); - optional sunset(time::ESPTime date, double elevation); + optional sunrise(double elevation); + optional sunset(double elevation); + optional sunrise(ESPTime date, double elevation); + optional sunset(ESPTime date, double elevation); double elevation(); double azimuth(); protected: internal::HorizontalCoordinate calc_coords_(); - optional calc_event_(bool rising, double zenith); - optional calc_event_(time::ESPTime date, bool rising, double zenith); + optional calc_event_(bool rising, double zenith); + optional calc_event_(ESPTime date, bool rising, double zenith); time::RealTimeClock *time_; internal::GeoLocation location_; diff --git a/esphome/components/sun/text_sensor/sun_text_sensor.h b/esphome/components/sun/text_sensor/sun_text_sensor.h index ad01d64ff1..ce7d21fb86 100644 --- a/esphome/components/sun/text_sensor/sun_text_sensor.h +++ b/esphome/components/sun/text_sensor/sun_text_sensor.h @@ -1,6 +1,8 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/time.h" + #include "esphome/components/sun/sun.h" #include "esphome/components/text_sensor/text_sensor.h" @@ -15,7 +17,7 @@ class SunTextSensor : public text_sensor::TextSensor, public PollingComponent { void set_format(const std::string &format) { format_ = format; } void update() override { - optional res; + optional res; if (this->sunrise_) { res = this->parent_->sunrise(this->elevation_); } else { diff --git a/esphome/components/time/automation.cpp b/esphome/components/time/automation.cpp index af2b6c720c..f7c1916ffe 100644 --- a/esphome/components/time/automation.cpp +++ b/esphome/components/time/automation.cpp @@ -1,5 +1,7 @@ #include "automation.h" + #include "esphome/core/log.h" + #include namespace esphome { diff --git a/esphome/components/time/automation.h b/esphome/components/time/automation.h index e97413e420..b5c8291533 100644 --- a/esphome/components/time/automation.h +++ b/esphome/components/time/automation.h @@ -1,7 +1,9 @@ #pragma once -#include "esphome/core/component.h" #include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/core/time.h" + #include "real_time_clock.h" #include diff --git a/esphome/components/time/real_time_clock.cpp b/esphome/components/time/real_time_clock.cpp index de76676a4d..10fa9597b9 100644 --- a/esphome/components/time/real_time_clock.cpp +++ b/esphome/components/time/real_time_clock.cpp @@ -52,171 +52,5 @@ void RealTimeClock::apply_timezone_() { tzset(); } -size_t ESPTime::strftime(char *buffer, size_t buffer_len, const char *format) { - struct tm c_tm = this->to_c_tm(); - return ::strftime(buffer, buffer_len, 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); - res.minute = uint8_t(c_tm->tm_min); - res.hour = uint8_t(c_tm->tm_hour); - res.day_of_week = uint8_t(c_tm->tm_wday + 1); - res.day_of_month = uint8_t(c_tm->tm_mday); - res.day_of_year = uint16_t(c_tm->tm_yday + 1); - res.month = uint8_t(c_tm->tm_mon + 1); - res.year = uint16_t(c_tm->tm_year + 1900); - res.is_dst = bool(c_tm->tm_isdst); - res.timestamp = c_time; - return res; -} -struct tm ESPTime::to_c_tm() { - struct tm c_tm {}; - c_tm.tm_sec = this->second; - c_tm.tm_min = this->minute; - c_tm.tm_hour = this->hour; - c_tm.tm_mday = this->day_of_month; - c_tm.tm_mon = this->month - 1; - c_tm.tm_year = this->year - 1900; - c_tm.tm_wday = this->day_of_week - 1; - c_tm.tm_yday = this->day_of_year - 1; - c_tm.tm_isdst = this->is_dst; - return c_tm; -} -std::string ESPTime::strftime(const std::string &format) { - std::string timestr; - timestr.resize(format.size() * 4); - struct tm c_tm = this->to_c_tm(); - size_t len = ::strftime(×tr[0], timestr.size(), format.c_str(), &c_tm); - while (len == 0) { - timestr.resize(timestr.size() * 2); - len = ::strftime(×tr[0], timestr.size(), format.c_str(), &c_tm); - } - timestr.resize(len); - return timestr; -} - -template bool increment_time_value(T ¤t, uint16_t begin, uint16_t end) { - current++; - if (current >= end) { - current = begin; - return true; - } - return false; -} - -static bool is_leap_year(uint32_t year) { return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0); } - -static uint8_t days_in_month(uint8_t month, uint16_t year) { - static const uint8_t DAYS_IN_MONTH[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - uint8_t days = DAYS_IN_MONTH[month]; - if (month == 2 && is_leap_year(year)) - return 29; - return days; -} - -void ESPTime::increment_second() { - this->timestamp++; - if (!increment_time_value(this->second, 0, 60)) - return; - - // second roll-over, increment minute - if (!increment_time_value(this->minute, 0, 60)) - return; - - // minute roll-over, increment hour - if (!increment_time_value(this->hour, 0, 24)) - return; - - // hour roll-over, increment day - increment_time_value(this->day_of_week, 1, 8); - - if (increment_time_value(this->day_of_month, 1, days_in_month(this->month, this->year) + 1)) { - // day of month roll-over, increment month - increment_time_value(this->month, 1, 13); - } - - uint16_t days_in_year = (this->year % 4 == 0) ? 366 : 365; - if (increment_time_value(this->day_of_year, 1, days_in_year + 1)) { - // day of year roll-over, increment year - this->year++; - } -} -void ESPTime::increment_day() { - this->timestamp += 86400; - - // increment day - increment_time_value(this->day_of_week, 1, 8); - - if (increment_time_value(this->day_of_month, 1, days_in_month(this->month, this->year) + 1)) { - // day of month roll-over, increment month - increment_time_value(this->month, 1, 13); - } - - uint16_t days_in_year = (this->year % 4 == 0) ? 366 : 365; - if (increment_time_value(this->day_of_year, 1, days_in_year + 1)) { - // day of year roll-over, increment year - this->year++; - } -} -void ESPTime::recalc_timestamp_utc(bool use_day_of_year) { - time_t res = 0; - - if (!this->fields_in_range()) { - this->timestamp = -1; - return; - } - - for (int i = 1970; i < this->year; i++) - res += is_leap_year(i) ? 366 : 365; - - if (use_day_of_year) { - res += this->day_of_year - 1; - } else { - for (int i = 1; i < this->month; i++) - res += days_in_month(i, this->year); - - res += this->day_of_month - 1; - } - - res *= 24; - res += this->hour; - res *= 60; - res += this->minute; - res *= 60; - res += this->second; - this->timestamp = res; -} - -int32_t ESPTime::timezone_offset() { - int32_t offset = 0; - time_t now = ::time(nullptr); - auto local = ESPTime::from_epoch_local(now); - auto utc = ESPTime::from_epoch_utc(now); - bool negative = utc.hour > local.hour && local.day_of_year <= utc.day_of_year; - - if (utc.minute > local.minute) { - local.minute += 60; - local.hour -= 1; - } - offset += (local.minute - utc.minute) * 60; - - if (negative) { - offset -= (utc.hour - local.hour) * 3600; - } else { - if (utc.hour > local.hour) { - local.hour += 24; - } - offset += (local.hour - utc.hour) * 3600; - } - return offset; -} - -bool ESPTime::operator<(ESPTime other) { return this->timestamp < other.timestamp; } -bool ESPTime::operator<=(ESPTime other) { return this->timestamp <= other.timestamp; } -bool ESPTime::operator==(ESPTime other) { return this->timestamp == other.timestamp; } -bool ESPTime::operator>=(ESPTime other) { return this->timestamp >= other.timestamp; } -bool ESPTime::operator>(ESPTime other) { return this->timestamp > other.timestamp; } - } // namespace time } // namespace esphome diff --git a/esphome/components/time/real_time_clock.h b/esphome/components/time/real_time_clock.h index 7f4afee306..a17168ae6f 100644 --- a/esphome/components/time/real_time_clock.h +++ b/esphome/components/time/real_time_clock.h @@ -1,106 +1,15 @@ #pragma once +#include +#include #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" -#include -#include -#include +#include "esphome/core/time.h" namespace esphome { namespace time { -/// A more user-friendly version of struct tm from time.h -struct ESPTime { - /** seconds after the minute [0-60] - * @note second is generally 0-59; the extra range is to accommodate leap seconds. - */ - uint8_t second; - /// minutes after the hour [0-59] - uint8_t minute; - /// hours since midnight [0-23] - uint8_t hour; - /// day of the week; sunday=1 [1-7] - uint8_t day_of_week; - /// day of the month [1-31] - uint8_t day_of_month; - /// day of the year [1-366] - uint16_t day_of_year; - /// month; january=1 [1-12] - uint8_t month; - /// year - uint16_t year; - /// daylight saving time flag - bool is_dst; - /// unix epoch time (seconds since UTC Midnight January 1, 1970) - time_t timestamp; - - /** Convert this ESPTime struct to a null-terminated c string buffer as specified by the format argument. - * Up to buffer_len bytes are written. - * - * @see https://www.gnu.org/software/libc/manual/html_node/Formatting-Calendar-Time.html#index-strftime - */ - size_t strftime(char *buffer, size_t buffer_len, const char *format); - - /** Convert this ESPTime struct to a string as specified by the format argument. - * @see https://www.gnu.org/software/libc/manual/html_node/Formatting-Calendar-Time.html#index-strftime - * - * @warning This method uses dynamically allocated strings which can cause heap fragmentation with some - * microcontrollers. - */ - std::string strftime(const std::string &format); - - /// Check if this ESPTime is valid (all fields in range and year is greater than 2018) - bool is_valid() const { return this->year >= 2019 && this->fields_in_range(); } - - /// Check if all time fields of this ESPTime are in range. - bool fields_in_range() const { - return this->second < 61 && this->minute < 60 && this->hour < 24 && this->day_of_week > 0 && - this->day_of_week < 8 && this->day_of_month > 0 && this->day_of_month < 32 && this->day_of_year > 0 && - this->day_of_year < 367 && this->month > 0 && this->month < 13; - } - - /// Convert a C tm struct instance with a C unix epoch timestamp to an ESPTime instance. - static ESPTime from_c_tm(struct tm *c_tm, time_t c_time); - - /** Convert an UTC epoch timestamp to a local time ESPTime instance. - * - * @param epoch Seconds since 1st January 1970. In UTC. - * @return The generated ESPTime - */ - static ESPTime from_epoch_local(time_t epoch) { - struct tm *c_tm = ::localtime(&epoch); - return ESPTime::from_c_tm(c_tm, epoch); - } - /** Convert an UTC epoch timestamp to a UTC time ESPTime instance. - * - * @param epoch Seconds since 1st January 1970. In UTC. - * @return The generated ESPTime - */ - static ESPTime from_epoch_utc(time_t epoch) { - struct tm *c_tm = ::gmtime(&epoch); - return ESPTime::from_c_tm(c_tm, epoch); - } - - /// Recalculate the timestamp field from the other fields of this ESPTime instance (must be UTC). - void recalc_timestamp_utc(bool use_day_of_year = true); - - /// Convert this ESPTime instance back to a tm struct. - struct tm to_c_tm(); - - static int32_t timezone_offset(); - - /// Increment this clock instance by one second. - void increment_second(); - /// Increment this clock instance by one day. - void increment_day(); - bool operator<(ESPTime other); - bool operator<=(ESPTime other); - bool operator==(ESPTime other); - bool operator>=(ESPTime other); - bool operator>(ESPTime other); -}; - /// The RealTimeClock class exposes common timekeeping functions via the device's local real-time clock. /// /// \note diff --git a/esphome/components/tm1637/tm1637.cpp b/esphome/components/tm1637/tm1637.cpp index 5b8cbc6004..434c6e65f3 100644 --- a/esphome/components/tm1637/tm1637.cpp +++ b/esphome/components/tm1637/tm1637.cpp @@ -368,16 +368,14 @@ uint8_t TM1637Display::printf(const char *format, ...) { return 0; } -#ifdef USE_TIME -uint8_t TM1637Display::strftime(uint8_t pos, const char *format, time::ESPTime time) { +uint8_t TM1637Display::strftime(uint8_t pos, const char *format, ESPTime time) { char buffer[64]; size_t ret = time.strftime(buffer, sizeof(buffer), format); if (ret > 0) return this->print(pos, buffer); return 0; } -uint8_t TM1637Display::strftime(const char *format, time::ESPTime time) { return this->strftime(0, format, time); } -#endif +uint8_t TM1637Display::strftime(const char *format, ESPTime time) { return this->strftime(0, format, time); } } // namespace tm1637 } // namespace esphome diff --git a/esphome/components/tm1637/tm1637.h b/esphome/components/tm1637/tm1637.h index 2fb572bc55..aba0071b12 100644 --- a/esphome/components/tm1637/tm1637.h +++ b/esphome/components/tm1637/tm1637.h @@ -3,13 +3,10 @@ #include "esphome/core/component.h" #include "esphome/core/defines.h" #include "esphome/core/hal.h" +#include "esphome/core/time.h" #include -#ifdef USE_TIME -#include "esphome/components/time/real_time_clock.h" -#endif - #ifdef USE_BINARY_SENSOR #include "esphome/components/binary_sensor/binary_sensor.h" #endif @@ -61,12 +58,10 @@ class TM1637Display : public PollingComponent { void add_tm1637_key(TM1637Key *tm1637_key) { this->tm1637_keys_.push_back(tm1637_key); } #endif -#ifdef USE_TIME /// Evaluate the strftime-format and print the result at the given position. - uint8_t strftime(uint8_t pos, const char *format, time::ESPTime time) __attribute__((format(strftime, 3, 0))); + uint8_t strftime(uint8_t pos, const char *format, ESPTime time) __attribute__((format(strftime, 3, 0))); /// Evaluate the strftime-format and print the result at position 0. - uint8_t strftime(const char *format, time::ESPTime time) __attribute__((format(strftime, 2, 0))); -#endif + uint8_t strftime(const char *format, ESPTime time) __attribute__((format(strftime, 2, 0))); protected: void bit_delay_(); diff --git a/esphome/components/tm1638/tm1638.cpp b/esphome/components/tm1638/tm1638.cpp index 24cb4122bf..a15b006046 100644 --- a/esphome/components/tm1638/tm1638.cpp +++ b/esphome/components/tm1638/tm1638.cpp @@ -211,16 +211,14 @@ uint8_t TM1638Component::printf(const char *format, ...) { return 0; } -#ifdef USE_TIME -uint8_t TM1638Component::strftime(uint8_t pos, const char *format, time::ESPTime time) { +uint8_t TM1638Component::strftime(uint8_t pos, const char *format, ESPTime time) { char buffer[64]; size_t ret = time.strftime(buffer, sizeof(buffer), format); if (ret > 0) return this->print(pos, buffer); return 0; } -uint8_t TM1638Component::strftime(const char *format, time::ESPTime time) { return this->strftime(0, format, time); } -#endif +uint8_t TM1638Component::strftime(const char *format, ESPTime time) { return this->strftime(0, format, time); } //////////////// SPI //////////////// diff --git a/esphome/components/tm1638/tm1638.h b/esphome/components/tm1638/tm1638.h index 2e1ac6fad3..add72cfdf3 100644 --- a/esphome/components/tm1638/tm1638.h +++ b/esphome/components/tm1638/tm1638.h @@ -1,16 +1,13 @@ #pragma once +#include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/defines.h" -#include "esphome/core/automation.h" #include "esphome/core/hal.h" +#include "esphome/core/time.h" #include -#ifdef USE_TIME -#include "esphome/components/time/real_time_clock.h" -#endif - namespace esphome { namespace tm1638 { @@ -52,12 +49,10 @@ class TM1638Component : public PollingComponent { void loop() override; uint8_t get_keys(); -#ifdef USE_TIME /// Evaluate the strftime-format and print the result at the given position. - uint8_t strftime(uint8_t pos, const char *format, time::ESPTime time) __attribute__((format(strftime, 3, 0))); + uint8_t strftime(uint8_t pos, const char *format, ESPTime time) __attribute__((format(strftime, 3, 0))); /// Evaluate the strftime-format and print the result at position 0. - uint8_t strftime(const char *format, time::ESPTime time) __attribute__((format(strftime, 2, 0))); -#endif + uint8_t strftime(const char *format, ESPTime time) __attribute__((format(strftime, 2, 0))); void set_led(int led_pos, bool led_on_off); diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 040b9b7ed5..7e6b1d53fe 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -508,7 +508,7 @@ void Tuya::send_wifi_status_() { void Tuya::send_local_time_() { std::vector payload; auto *time_id = *this->time_id_; - time::ESPTime now = time_id->now(); + ESPTime now = time_id->now(); if (now.is_valid()) { uint8_t year = now.year - 2000; uint8_t month = now.month; diff --git a/esphome/components/tuya/tuya.h b/esphome/components/tuya/tuya.h index 8d6153482f..b9901dd5e7 100644 --- a/esphome/components/tuya/tuya.h +++ b/esphome/components/tuya/tuya.h @@ -7,6 +7,7 @@ #ifdef USE_TIME #include "esphome/components/time/real_time_clock.h" +#include "esphome/core/time.h" #endif #include diff --git a/esphome/core/time.cpp b/esphome/core/time.cpp new file mode 100644 index 0000000000..c03506fd2a --- /dev/null +++ b/esphome/core/time.cpp @@ -0,0 +1,171 @@ +#include "time.h" // NOLINT + +namespace esphome { + +size_t ESPTime::strftime(char *buffer, size_t buffer_len, const char *format) { + struct tm c_tm = this->to_c_tm(); + return ::strftime(buffer, buffer_len, 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); + res.minute = uint8_t(c_tm->tm_min); + res.hour = uint8_t(c_tm->tm_hour); + res.day_of_week = uint8_t(c_tm->tm_wday + 1); + res.day_of_month = uint8_t(c_tm->tm_mday); + res.day_of_year = uint16_t(c_tm->tm_yday + 1); + res.month = uint8_t(c_tm->tm_mon + 1); + res.year = uint16_t(c_tm->tm_year + 1900); + res.is_dst = bool(c_tm->tm_isdst); + res.timestamp = c_time; + return res; +} +struct tm ESPTime::to_c_tm() { + struct tm c_tm {}; + c_tm.tm_sec = this->second; + c_tm.tm_min = this->minute; + c_tm.tm_hour = this->hour; + c_tm.tm_mday = this->day_of_month; + c_tm.tm_mon = this->month - 1; + c_tm.tm_year = this->year - 1900; + c_tm.tm_wday = this->day_of_week - 1; + c_tm.tm_yday = this->day_of_year - 1; + c_tm.tm_isdst = this->is_dst; + return c_tm; +} +std::string ESPTime::strftime(const std::string &format) { + std::string timestr; + timestr.resize(format.size() * 4); + struct tm c_tm = this->to_c_tm(); + size_t len = ::strftime(×tr[0], timestr.size(), format.c_str(), &c_tm); + while (len == 0) { + timestr.resize(timestr.size() * 2); + len = ::strftime(×tr[0], timestr.size(), format.c_str(), &c_tm); + } + timestr.resize(len); + return timestr; +} + +void ESPTime::increment_second() { + this->timestamp++; + if (!increment_time_value(this->second, 0, 60)) + return; + + // second roll-over, increment minute + if (!increment_time_value(this->minute, 0, 60)) + return; + + // minute roll-over, increment hour + if (!increment_time_value(this->hour, 0, 24)) + return; + + // hour roll-over, increment day + increment_time_value(this->day_of_week, 1, 8); + + if (increment_time_value(this->day_of_month, 1, days_in_month(this->month, this->year) + 1)) { + // day of month roll-over, increment month + increment_time_value(this->month, 1, 13); + } + + uint16_t days_in_year = (this->year % 4 == 0) ? 366 : 365; + if (increment_time_value(this->day_of_year, 1, days_in_year + 1)) { + // day of year roll-over, increment year + this->year++; + } +} +void ESPTime::increment_day() { + this->timestamp += 86400; + + // increment day + increment_time_value(this->day_of_week, 1, 8); + + if (increment_time_value(this->day_of_month, 1, days_in_month(this->month, this->year) + 1)) { + // day of month roll-over, increment month + increment_time_value(this->month, 1, 13); + } + + uint16_t days_in_year = (this->year % 4 == 0) ? 366 : 365; + if (increment_time_value(this->day_of_year, 1, days_in_year + 1)) { + // day of year roll-over, increment year + this->year++; + } +} +void ESPTime::recalc_timestamp_utc(bool use_day_of_year) { + time_t res = 0; + + if (!this->fields_in_range()) { + this->timestamp = -1; + return; + } + + for (int i = 1970; i < this->year; i++) + res += is_leap_year(i) ? 366 : 365; + + if (use_day_of_year) { + res += this->day_of_year - 1; + } else { + for (int i = 1; i < this->month; i++) + res += days_in_month(i, this->year); + + res += this->day_of_month - 1; + } + + res *= 24; + res += this->hour; + res *= 60; + res += this->minute; + res *= 60; + res += this->second; + this->timestamp = res; +} + +int32_t ESPTime::timezone_offset() { + int32_t offset = 0; + time_t now = ::time(nullptr); + auto local = ESPTime::from_epoch_local(now); + auto utc = ESPTime::from_epoch_utc(now); + bool negative = utc.hour > local.hour && local.day_of_year <= utc.day_of_year; + + if (utc.minute > local.minute) { + local.minute += 60; + local.hour -= 1; + } + offset += (local.minute - utc.minute) * 60; + + if (negative) { + offset -= (utc.hour - local.hour) * 3600; + } else { + if (utc.hour > local.hour) { + local.hour += 24; + } + offset += (local.hour - utc.hour) * 3600; + } + return offset; +} + +bool ESPTime::operator<(ESPTime other) { return this->timestamp < other.timestamp; } +bool ESPTime::operator<=(ESPTime other) { return this->timestamp <= other.timestamp; } +bool ESPTime::operator==(ESPTime other) { return this->timestamp == other.timestamp; } +bool ESPTime::operator>=(ESPTime other) { return this->timestamp >= other.timestamp; } +bool ESPTime::operator>(ESPTime other) { return this->timestamp > other.timestamp; } + +template bool increment_time_value(T ¤t, uint16_t begin, uint16_t end) { + current++; + if (current >= end) { + current = begin; + return true; + } + return false; +} + +static bool is_leap_year(uint32_t year) { return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0); } + +static uint8_t days_in_month(uint8_t month, uint16_t year) { + static const uint8_t DAYS_IN_MONTH[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + uint8_t days = DAYS_IN_MONTH[month]; + if (month == 2 && is_leap_year(year)) + return 29; + return days; +} + +} // namespace esphome diff --git a/esphome/core/time.h b/esphome/core/time.h new file mode 100644 index 0000000000..e1bdc8c839 --- /dev/null +++ b/esphome/core/time.h @@ -0,0 +1,105 @@ +#pragma once + +#include +#include +#include + +namespace esphome { + +template bool increment_time_value(T ¤t, uint16_t begin, uint16_t end); + +static bool is_leap_year(uint32_t year); + +static uint8_t days_in_month(uint8_t month, uint16_t year); + +/// A more user-friendly version of struct tm from time.h +struct ESPTime { + /** seconds after the minute [0-60] + * @note second is generally 0-59; the extra range is to accommodate leap seconds. + */ + uint8_t second; + /// minutes after the hour [0-59] + uint8_t minute; + /// hours since midnight [0-23] + uint8_t hour; + /// day of the week; sunday=1 [1-7] + uint8_t day_of_week; + /// day of the month [1-31] + uint8_t day_of_month; + /// day of the year [1-366] + uint16_t day_of_year; + /// month; january=1 [1-12] + uint8_t month; + /// year + uint16_t year; + /// daylight saving time flag + bool is_dst; + /// unix epoch time (seconds since UTC Midnight January 1, 1970) + time_t timestamp; + + /** Convert this ESPTime struct to a null-terminated c string buffer as specified by the format argument. + * Up to buffer_len bytes are written. + * + * @see https://www.gnu.org/software/libc/manual/html_node/Formatting-Calendar-Time.html#index-strftime + */ + size_t strftime(char *buffer, size_t buffer_len, const char *format); + + /** Convert this ESPTime struct to a string as specified by the format argument. + * @see https://www.gnu.org/software/libc/manual/html_node/Formatting-Calendar-Time.html#index-strftime + * + * @warning This method uses dynamically allocated strings which can cause heap fragmentation with some + * microcontrollers. + */ + std::string strftime(const std::string &format); + + /// Check if this ESPTime is valid (all fields in range and year is greater than 2018) + bool is_valid() const { return this->year >= 2019 && this->fields_in_range(); } + + /// Check if all time fields of this ESPTime are in range. + bool fields_in_range() const { + return this->second < 61 && this->minute < 60 && this->hour < 24 && this->day_of_week > 0 && + this->day_of_week < 8 && this->day_of_month > 0 && this->day_of_month < 32 && this->day_of_year > 0 && + this->day_of_year < 367 && this->month > 0 && this->month < 13; + } + + /// Convert a C tm struct instance with a C unix epoch timestamp to an ESPTime instance. + static ESPTime from_c_tm(struct tm *c_tm, time_t c_time); + + /** Convert an UTC epoch timestamp to a local time ESPTime instance. + * + * @param epoch Seconds since 1st January 1970. In UTC. + * @return The generated ESPTime + */ + static ESPTime from_epoch_local(time_t epoch) { + struct tm *c_tm = ::localtime(&epoch); + return ESPTime::from_c_tm(c_tm, epoch); + } + /** Convert an UTC epoch timestamp to a UTC time ESPTime instance. + * + * @param epoch Seconds since 1st January 1970. In UTC. + * @return The generated ESPTime + */ + static ESPTime from_epoch_utc(time_t epoch) { + struct tm *c_tm = ::gmtime(&epoch); + return ESPTime::from_c_tm(c_tm, epoch); + } + + /// Recalculate the timestamp field from the other fields of this ESPTime instance (must be UTC). + void recalc_timestamp_utc(bool use_day_of_year = true); + + /// Convert this ESPTime instance back to a tm struct. + struct tm to_c_tm(); + + static int32_t timezone_offset(); + + /// Increment this clock instance by one second. + void increment_second(); + /// Increment this clock instance by one day. + void increment_day(); + bool operator<(ESPTime other); + bool operator<=(ESPTime other); + bool operator==(ESPTime other); + bool operator>=(ESPTime other); + bool operator>(ESPTime other); +}; +} // namespace esphome