Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ ESPDate is a tiny C++17 helper for ESP32 projects that makes working with dates
- **Optional NTP bootstrap**: call `init` with `ESPDateConfig` containing both `timeZone` and `ntpServer` to set TZ and start SNTP after Arduino/WiFi is ready.
- **NTP sync callback + manual re-sync**: register `setNtpSyncCallback(...)` with a function, lambda, or `std::bind`, call `syncNTP()` anytime to trigger an immediate refresh, and optionally override SNTP interval via `ntpSyncIntervalMs` / `setNtpSyncIntervalMs(...)`.
- **Optional PSRAM-backed config/state buffers**: `ESPDateConfig::usePSRAMBuffers` routes ESPDate-owned text state (timezone/NTP/scoped TZ restore buffers) through `ESPBufferManager` with automatic fallback.
- **Explicit lifecycle cleanup**: `deinit()` unregisters ESPDate-owned SNTP callback hooks; the destructor calls it automatically.
- **Explicit lifecycle cleanup**: `deinit()` unregisters ESPDate-owned SNTP callback hooks, clears runtime config buffers, and is safe to call repeatedly; the destructor calls it automatically.
- **Init-state introspection**: `isInitialized()` reports whether `init(...)` has been called without a matching `deinit()`.
- **Last sync tracking**: `hasLastNtpSync()` / `lastNtpSync()` expose the latest SNTP sync timestamp kept inside `ESPDate`.
- **Last sync string helpers**: `lastNtpSyncStringLocal/Utc` provide direct formatting helpers for `lastNtpSync`.
- **Local breakdown helpers**: `nowLocal()` / `toLocal()` surface the broken-out local time (with UTC offset) for quick DST/debug checks; feed sunrise/sunset results into `toLocal` to read them in local time.
Expand Down Expand Up @@ -118,6 +119,20 @@ void setup() {
std::string utcString = date.nowUtcString();
Serial.printf("UTC now (string): %s\n", utcString.c_str());
}

void loop() {
// Example teardown path (mode switch / OTA / feature shutdown).
static bool released = false;
if (!released && millis() > 60000UL) {
if (date.isInitialized()) {
date.deinit();
}
if (solar.isInitialized()) {
solar.deinit();
}
released = true;
}
}
```

### Working With Local Time (UI) vs UTC (storage/logic)
Expand Down Expand Up @@ -186,6 +201,7 @@ public:
~ESPDate();
void init(const ESPDateConfig &config);
void deinit();
bool isInitialized() const;
void setNtpSyncCallback(NtpSyncCallback callback);
template <typename Callable>
void setNtpSyncCallback(Callable&& callback); // capturing lambda/std::bind/functor
Expand Down
8 changes: 7 additions & 1 deletion examples/basic_date/basic_date.ino
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <functional>

ESPDate date;
bool releasedDateResources = false;

class SyncObserver {
public:
Expand Down Expand Up @@ -95,5 +96,10 @@ void setup() {
}

void loop() {
// Intentionally empty.
// Demonstrate explicit teardown in long-running sketches.
if (!releasedDateResources && millis() > 60000UL && date.isInitialized()) {
date.deinit();
releasedDateResources = true;
Serial.println("ESPDate deinitialized.");
}
}
11 changes: 10 additions & 1 deletion src/esp_date/date.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,17 @@ ESPDate::~ESPDate() {
void ESPDate::deinit() {
ntpSyncCallback_ = nullptr;
ntpSyncCallbackCallable_ = NtpSyncCallable{};
usePSRAMBuffers_ = false;
hasLastNtpSync_ = false;
lastNtpSync_ = DateTime{};
hasLocation_ = false;
latitude_ = 0.0f;
longitude_ = 0.0f;
ntpSyncIntervalMs_ = 0;
const bool usePSRAM = usePSRAMBuffers_;
timeZone_ = DateString(DateAllocator<char>(usePSRAM));
ntpServer_ = DateString(DateAllocator<char>(usePSRAM));
usePSRAMBuffers_ = false;
initialized_ = false;

if (activeNtpSyncOwner_ == this) {
activeNtpSyncOwner_ = nullptr;
Expand Down Expand Up @@ -229,6 +237,7 @@ void ESPDate::init(const ESPDateConfig& config) {
setenv("TZ", timeZone_.c_str(), 1);
tzset();
}
initialized_ = true;
}

void ESPDate::setNtpSyncCallback(NtpSyncCallback callback) {
Expand Down
4 changes: 4 additions & 0 deletions src/esp_date/date.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ class ESPDate {
~ESPDate();
void init(const ESPDateConfig& config);
void deinit();
bool isInitialized() const {
return initialized_;
}
// Optional SNTP sync notification. Pass nullptr to clear.
void setNtpSyncCallback(NtpSyncCallback callback);
// Accepts capturing lambdas / std::bind / functors.
Expand Down Expand Up @@ -298,4 +301,5 @@ class ESPDate {
static NtpSyncCallable activeNtpSyncCallbackCallable_;
static ESPDate* activeNtpSyncOwner_;
bool hasLocation_ = false;
bool initialized_ = false;
};
39 changes: 39 additions & 0 deletions test/test_esp_date/test_esp_date.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,42 @@ ESPDate date;
static const float kBudapestLat = 47.4979f;
static const float kBudapestLon = 19.0402f;

static void test_deinit_is_safe_before_init() {
ESPDate monitor;
TEST_ASSERT_FALSE(monitor.isInitialized());

monitor.deinit();
TEST_ASSERT_FALSE(monitor.isInitialized());
}

static void test_deinit_is_idempotent() {
ESPDate monitor;
monitor.init(ESPDateConfig{0.0f, 0.0f, "UTC0", nullptr});
TEST_ASSERT_TRUE(monitor.isInitialized());

monitor.deinit();
TEST_ASSERT_FALSE(monitor.isInitialized());

monitor.deinit();
TEST_ASSERT_FALSE(monitor.isInitialized());
}

static void test_reinit_after_deinit() {
ESPDate monitor;
monitor.init(ESPDateConfig{0.0f, 0.0f, "UTC0", nullptr});
TEST_ASSERT_TRUE(monitor.isInitialized());

monitor.deinit();
TEST_ASSERT_FALSE(monitor.isInitialized());

monitor.init(ESPDateConfig{kBudapestLat, kBudapestLon, "CET-1CEST,M3.5.0/2,M10.5.0/3", nullptr});
TEST_ASSERT_TRUE(monitor.isInitialized());
TEST_ASSERT_TRUE(monitor.sunrise(monitor.fromUtc(2024, 6, 1)).ok);

monitor.deinit();
TEST_ASSERT_FALSE(monitor.isInitialized());
}

static void test_add_days_and_differences() {
DateTime base = date.fromUnixSeconds(1704067200); // 2024-01-01T00:00:00Z
DateTime plus = date.addDays(base, 1);
Expand Down Expand Up @@ -331,6 +367,9 @@ void setup() {
tzset();
delay(2000);
UNITY_BEGIN();
RUN_TEST(test_deinit_is_safe_before_init);
RUN_TEST(test_deinit_is_idempotent);
RUN_TEST(test_reinit_after_deinit);
RUN_TEST(test_add_days_and_differences);
RUN_TEST(test_add_months_clamps_day_in_leap_year);
RUN_TEST(test_start_and_end_of_day_utc);
Expand Down