Skip to content

Commit 82e4c19

Browse files
authored
Merge pull request #1 from ESPToolKit/feature/teardown/teardown-contract-esp-date
Implement teardown contract for ESPDate
2 parents 389fc6a + 99a181a commit 82e4c19

File tree

5 files changed

+77
-3
lines changed

5 files changed

+77
-3
lines changed

README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ ESPDate is a tiny C++17 helper for ESP32 projects that makes working with dates
2222
- **Optional NTP bootstrap**: call `init` with `ESPDateConfig` containing both `timeZone` and `ntpServer` to set TZ and start SNTP after Arduino/WiFi is ready.
2323
- **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(...)`.
2424
- **Optional PSRAM-backed config/state buffers**: `ESPDateConfig::usePSRAMBuffers` routes ESPDate-owned text state (timezone/NTP/scoped TZ restore buffers) through `ESPBufferManager` with automatic fallback.
25-
- **Explicit lifecycle cleanup**: `deinit()` unregisters ESPDate-owned SNTP callback hooks; the destructor calls it automatically.
25+
- **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.
26+
- **Init-state introspection**: `isInitialized()` reports whether `init(...)` has been called without a matching `deinit()`.
2627
- **Last sync tracking**: `hasLastNtpSync()` / `lastNtpSync()` expose the latest SNTP sync timestamp kept inside `ESPDate`.
2728
- **Last sync string helpers**: `lastNtpSyncStringLocal/Utc` provide direct formatting helpers for `lastNtpSync`.
2829
- **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.
@@ -118,6 +119,20 @@ void setup() {
118119
std::string utcString = date.nowUtcString();
119120
Serial.printf("UTC now (string): %s\n", utcString.c_str());
120121
}
122+
123+
void loop() {
124+
// Example teardown path (mode switch / OTA / feature shutdown).
125+
static bool released = false;
126+
if (!released && millis() > 60000UL) {
127+
if (date.isInitialized()) {
128+
date.deinit();
129+
}
130+
if (solar.isInitialized()) {
131+
solar.deinit();
132+
}
133+
released = true;
134+
}
135+
}
121136
```
122137

123138
### Working With Local Time (UI) vs UTC (storage/logic)
@@ -186,6 +201,7 @@ public:
186201
~ESPDate();
187202
void init(const ESPDateConfig &config);
188203
void deinit();
204+
bool isInitialized() const;
189205
void setNtpSyncCallback(NtpSyncCallback callback);
190206
template <typename Callable>
191207
void setNtpSyncCallback(Callable&& callback); // capturing lambda/std::bind/functor

examples/basic_date/basic_date.ino

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <functional>
44

55
ESPDate date;
6+
bool releasedDateResources = false;
67

78
class SyncObserver {
89
public:
@@ -95,5 +96,10 @@ void setup() {
9596
}
9697

9798
void loop() {
98-
// Intentionally empty.
99+
// Demonstrate explicit teardown in long-running sketches.
100+
if (!releasedDateResources && millis() > 60000UL && date.isInitialized()) {
101+
date.deinit();
102+
releasedDateResources = true;
103+
Serial.println("ESPDate deinitialized.");
104+
}
99105
}

src/esp_date/date.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,17 @@ ESPDate::~ESPDate() {
191191
void ESPDate::deinit() {
192192
ntpSyncCallback_ = nullptr;
193193
ntpSyncCallbackCallable_ = NtpSyncCallable{};
194-
usePSRAMBuffers_ = false;
195194
hasLastNtpSync_ = false;
196195
lastNtpSync_ = DateTime{};
196+
hasLocation_ = false;
197+
latitude_ = 0.0f;
198+
longitude_ = 0.0f;
199+
ntpSyncIntervalMs_ = 0;
200+
const bool usePSRAM = usePSRAMBuffers_;
201+
timeZone_ = DateString(DateAllocator<char>(usePSRAM));
202+
ntpServer_ = DateString(DateAllocator<char>(usePSRAM));
203+
usePSRAMBuffers_ = false;
204+
initialized_ = false;
197205

198206
if (activeNtpSyncOwner_ == this) {
199207
activeNtpSyncOwner_ = nullptr;
@@ -229,6 +237,7 @@ void ESPDate::init(const ESPDateConfig& config) {
229237
setenv("TZ", timeZone_.c_str(), 1);
230238
tzset();
231239
}
240+
initialized_ = true;
232241
}
233242

234243
void ESPDate::setNtpSyncCallback(NtpSyncCallback callback) {

src/esp_date/date.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ class ESPDate {
7979
~ESPDate();
8080
void init(const ESPDateConfig& config);
8181
void deinit();
82+
bool isInitialized() const {
83+
return initialized_;
84+
}
8285
// Optional SNTP sync notification. Pass nullptr to clear.
8386
void setNtpSyncCallback(NtpSyncCallback callback);
8487
// Accepts capturing lambdas / std::bind / functors.
@@ -298,4 +301,5 @@ class ESPDate {
298301
static NtpSyncCallable activeNtpSyncCallbackCallable_;
299302
static ESPDate* activeNtpSyncOwner_;
300303
bool hasLocation_ = false;
304+
bool initialized_ = false;
301305
};

test/test_esp_date/test_esp_date.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,42 @@ ESPDate date;
1111
static const float kBudapestLat = 47.4979f;
1212
static const float kBudapestLon = 19.0402f;
1313

14+
static void test_deinit_is_safe_before_init() {
15+
ESPDate monitor;
16+
TEST_ASSERT_FALSE(monitor.isInitialized());
17+
18+
monitor.deinit();
19+
TEST_ASSERT_FALSE(monitor.isInitialized());
20+
}
21+
22+
static void test_deinit_is_idempotent() {
23+
ESPDate monitor;
24+
monitor.init(ESPDateConfig{0.0f, 0.0f, "UTC0", nullptr});
25+
TEST_ASSERT_TRUE(monitor.isInitialized());
26+
27+
monitor.deinit();
28+
TEST_ASSERT_FALSE(monitor.isInitialized());
29+
30+
monitor.deinit();
31+
TEST_ASSERT_FALSE(monitor.isInitialized());
32+
}
33+
34+
static void test_reinit_after_deinit() {
35+
ESPDate monitor;
36+
monitor.init(ESPDateConfig{0.0f, 0.0f, "UTC0", nullptr});
37+
TEST_ASSERT_TRUE(monitor.isInitialized());
38+
39+
monitor.deinit();
40+
TEST_ASSERT_FALSE(monitor.isInitialized());
41+
42+
monitor.init(ESPDateConfig{kBudapestLat, kBudapestLon, "CET-1CEST,M3.5.0/2,M10.5.0/3", nullptr});
43+
TEST_ASSERT_TRUE(monitor.isInitialized());
44+
TEST_ASSERT_TRUE(monitor.sunrise(monitor.fromUtc(2024, 6, 1)).ok);
45+
46+
monitor.deinit();
47+
TEST_ASSERT_FALSE(monitor.isInitialized());
48+
}
49+
1450
static void test_add_days_and_differences() {
1551
DateTime base = date.fromUnixSeconds(1704067200); // 2024-01-01T00:00:00Z
1652
DateTime plus = date.addDays(base, 1);
@@ -331,6 +367,9 @@ void setup() {
331367
tzset();
332368
delay(2000);
333369
UNITY_BEGIN();
370+
RUN_TEST(test_deinit_is_safe_before_init);
371+
RUN_TEST(test_deinit_is_idempotent);
372+
RUN_TEST(test_reinit_after_deinit);
334373
RUN_TEST(test_add_days_and_differences);
335374
RUN_TEST(test_add_months_clamps_day_in_leap_year);
336375
RUN_TEST(test_start_and_end_of_day_utc);

0 commit comments

Comments
 (0)