From ce7f666d6b23ce0193cb0ba07db4164cddb52351 Mon Sep 17 00:00:00 2001
From: Tomasz Duda <tomaszduda23@gmail.com>
Date: Fri, 12 Jul 2024 16:03:16 +0200
Subject: [PATCH] add real time clock

---
 esphome/components/time/__init__.py           | 13 ++++++++---
 esphome/components/time/real_time_clock.cpp   | 22 +++++++++++++++++--
 esphome/components/time/real_time_clock.h     |  8 +++----
 esphome/config_validation.py                  |  1 +
 script/test_build_components                  |  9 --------
 .../datetime/exclude.test.nrf52-adafruit.yaml |  0
 .../datetime/exclude.test.nrf52-mcumgr.yaml   |  0
 7 files changed, 35 insertions(+), 18 deletions(-)
 delete mode 100644 tests/components/datetime/exclude.test.nrf52-adafruit.yaml
 delete mode 100644 tests/components/datetime/exclude.test.nrf52-mcumgr.yaml

diff --git a/esphome/components/time/__init__.py b/esphome/components/time/__init__.py
index c888705ba2..dff646ecbd 100644
--- a/esphome/components/time/__init__.py
+++ b/esphome/components/time/__init__.py
@@ -25,8 +25,9 @@ from esphome.const import (
     CONF_HOUR,
     CONF_MINUTE,
 )
-from esphome.core import coroutine_with_priority
+from esphome.core import coroutine_with_priority, CORE
 from esphome.automation import Condition
+from esphome.components.zephyr import zephyr_add_prj_conf
 
 _LOGGER = logging.getLogger(__name__)
 
@@ -269,7 +270,10 @@ def validate_tz(value: str) -> str:
 
 TIME_SCHEMA = cv.Schema(
     {
-        cv.Optional(CONF_TIMEZONE, default=detect_tz): validate_tz,
+        cv.Optional(CONF_TIMEZONE, default=detect_tz): cv.All(
+            cv.only_with_arduino_or_esp_idf,
+            validate_tz,
+        ),
         cv.Optional(CONF_ON_TIME): automation.validate_automation(
             {
                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CronTrigger),
@@ -294,7 +298,8 @@ TIME_SCHEMA = cv.Schema(
 
 
 async def setup_time_core_(time_var, config):
-    cg.add(time_var.set_timezone(config[CONF_TIMEZONE]))
+    if not CORE.using_zephyr:
+        cg.add(time_var.set_timezone(config[CONF_TIMEZONE]))
 
     for conf in config.get(CONF_ON_TIME, []):
         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], time_var)
@@ -328,6 +333,8 @@ async def register_time(time_var, config):
 
 @coroutine_with_priority(100.0)
 async def to_code(config):
+    if CORE.using_zephyr:
+        zephyr_add_prj_conf("POSIX_CLOCK", True)
     cg.add_define("USE_TIME")
     cg.add_global(time_ns.using)
 
diff --git a/esphome/components/time/real_time_clock.cpp b/esphome/components/time/real_time_clock.cpp
index 2b9a95c6bd..556889fd3d 100644
--- a/esphome/components/time/real_time_clock.cpp
+++ b/esphome/components/time/real_time_clock.cpp
@@ -2,13 +2,15 @@
 #include "esphome/core/log.h"
 #ifdef USE_HOST
 #include <sys/time.h>
+#elif defined(USE_ZEPHYR)
+#include <zephyr/posix/time.h>
 #else
 #include "lwip/opt.h"
 #endif
 #ifdef USE_ESP8266
 #include "sys/time.h"
 #endif
-#ifdef USE_RP2040
+#if defined(USE_RP2040) || defined(USE_ZEPHYR)
 #include <sys/time.h>
 #endif
 #include <cerrno>
@@ -22,15 +24,18 @@ static const char *const TAG = "time";
 
 RealTimeClock::RealTimeClock() = default;
 void RealTimeClock::call_setup() {
+#ifndef USE_ZEPHYR
   this->apply_timezone_();
+#endif
   PollingComponent::call_setup();
 }
 void RealTimeClock::synchronize_epoch_(uint32_t epoch) {
+  ESP_LOGVV(TAG, "Got epoch %" PRIu32, epoch);
   // Update UTC epoch time.
+#ifndef USE_ZEPHYR
   struct timeval timev {
     .tv_sec = static_cast<time_t>(epoch), .tv_usec = 0,
   };
-  ESP_LOGVV(TAG, "Got epoch %" PRIu32, epoch);
   struct timezone tz = {0, 0};
   int ret = settimeofday(&timev, &tz);
   if (ret == EINVAL) {
@@ -45,7 +50,18 @@ void RealTimeClock::synchronize_epoch_(uint32_t epoch) {
   if (ret != 0) {
     ESP_LOGW(TAG, "setimeofday() failed with code %d", ret);
   }
+#else
+  struct timespec ts;
+  ts.tv_nsec = 0;
+  ts.tv_sec = static_cast<time_t>(epoch);
 
+  int ret = clock_settime(CLOCK_REALTIME, &ts);
+
+  if (ret != 0) {
+    ESP_LOGW(TAG, "clock_settime() failed with code %d", ret);
+  }
+
+#endif
   auto time = this->now();
   ESP_LOGD(TAG, "Synchronized time: %04d-%02d-%02d %02d:%02d:%02d", time.year, time.month, time.day_of_month, time.hour,
            time.minute, time.second);
@@ -53,10 +69,12 @@ void RealTimeClock::synchronize_epoch_(uint32_t epoch) {
   this->time_sync_callback_.call();
 }
 
+#ifndef USE_ZEPHYR
 void RealTimeClock::apply_timezone_() {
   setenv("TZ", this->timezone_.c_str(), 1);
   tzset();
 }
+#endif
 
 }  // namespace time
 }  // namespace esphome
diff --git a/esphome/components/time/real_time_clock.h b/esphome/components/time/real_time_clock.h
index a17168ae6f..db6a2fee65 100644
--- a/esphome/components/time/real_time_clock.h
+++ b/esphome/components/time/real_time_clock.h
@@ -19,13 +19,13 @@ namespace time {
 class RealTimeClock : public PollingComponent {
  public:
   explicit RealTimeClock();
-
+#ifndef USE_ZEPHYR
   /// Set the time zone.
   void set_timezone(const std::string &tz) { this->timezone_ = tz; }
 
   /// Get the time zone currently in use.
   std::string get_timezone() { return this->timezone_; }
-
+#endif
   /// Get the time in the currently defined timezone.
   ESPTime now() { return ESPTime::from_epoch_local(this->timestamp_now()); }
 
@@ -44,10 +44,10 @@ class RealTimeClock : public PollingComponent {
  protected:
   /// Report a unix epoch as current time.
   void synchronize_epoch_(uint32_t epoch);
-
+#ifndef USE_ZEPHYR
   std::string timezone_{};
   void apply_timezone_();
-
+#endif
   CallbackManager<void()> time_sync_callback_;
 };
 
diff --git a/esphome/config_validation.py b/esphome/config_validation.py
index b3a9a3a078..6318d822d8 100644
--- a/esphome/config_validation.py
+++ b/esphome/config_validation.py
@@ -609,6 +609,7 @@ only_on_nrf52 = only_on(PLATFORM_NRF52)
 only_with_arduino = only_with_framework("arduino")
 only_with_esp_idf = only_with_framework("esp-idf")
 only_with_zephyr = only_with_framework("zephyr")
+only_with_arduino_or_esp_idf = only_with_framework(["arduino", "esp-idf"])
 
 
 # Adapted from:
diff --git a/script/test_build_components b/script/test_build_components
index 6157db4d40..e885294b99 100755
--- a/script/test_build_components
+++ b/script/test_build_components
@@ -58,10 +58,6 @@ for f in ./tests/components/$target_component/*.*.yaml; do
 
   IFS='.' read -r -a file_name <<< "${folder_name[4]}"
   test_name="${file_name[0]}"
-  if [ "$test_name" = "exclude" ]; then
-    # this is not a test file. we need to skip it.
-    continue
-  fi
   target_platform="${file_name[1]}"
   file_name_parts=${#file_name[@]}
 
@@ -73,11 +69,6 @@ for f in ./tests/components/$target_component/*.*.yaml; do
       IFS='.' read -r -a file_name <<< "${folder_name[3]}"
       target_platform="${file_name[1]}"
 
-      if [ -f "./tests/components/$target_component/exclude.$test_name.$target_platform.yaml" ]; then
-        echo "Component $target_component is excluded from testing on $target_platform"
-        continue
-      fi
-
       start_esphome
     done
 
diff --git a/tests/components/datetime/exclude.test.nrf52-adafruit.yaml b/tests/components/datetime/exclude.test.nrf52-adafruit.yaml
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/tests/components/datetime/exclude.test.nrf52-mcumgr.yaml b/tests/components/datetime/exclude.test.nrf52-mcumgr.yaml
deleted file mode 100644
index e69de29bb2..0000000000