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
11 changes: 11 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
BasedOnStyle: LLVM
ColumnLimit: 100
BinPackArguments: false
BinPackParameters: false
AllowAllArgumentsOnNextLine: false
AlignAfterOpenBracket: BlockIndent
UseTab: ForIndentation
IndentWidth: 4
TabWidth: 4
ContinuationIndentWidth: 4
AllowShortFunctionsOnASingleLine: None
11 changes: 11 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
root = true

[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8

[*.{c,cc,cpp,h,hpp,ino}]
indent_style = tab
indent_size = tab
tab_width = 4
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
.venv
build/
build_prev_runner/
.vscode
19 changes: 19 additions & 0 deletions .vscode/bin/clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env bash

set -euo pipefail

if command -v clang-format >/dev/null 2>&1; then
exec clang-format "$@"
fi

_home_dir="${HOME:-}"
if [ -n "$_home_dir" ]; then
_candidate="$(ls -1d "$_home_dir"/.vscode/extensions/ms-vscode.cpptools-*-linux-x64/LLVM/bin/clang-format 2>/dev/null | tail -n 1 || true)"
if [ -n "$_candidate" ] && [ -x "$_candidate" ]; then
exec "$_candidate" "$@"
fi
fi

echo "clang-format executable not found." >&2
echo "Install clang-format system-wide or install/update ms-vscode.cpptools." >&2
exit 127
9 changes: 9 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"recommendations": [
"pioarduino.pioarduino-ide",
"xaver.clang-format"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
}
30 changes: 30 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"files.associations": {
"*.ino": "cpp"
},
"editor.defaultFormatter": "xaver.clang-format",
"C_Cpp.formatting": "Disabled",
"clang-format.style": "file",
"clang-format.executable": "${workspaceRoot}/.vscode/bin/clang-format",
"[cpp]": {
"editor.defaultFormatter": "xaver.clang-format",
"editor.detectIndentation": false,
"editor.insertSpaces": false,
"editor.tabSize": 4,
"editor.formatOnSave": true
},
"[c]": {
"editor.defaultFormatter": "xaver.clang-format",
"editor.detectIndentation": false,
"editor.insertSpaces": false,
"editor.tabSize": 4,
"editor.formatOnSave": true
},
"[arduino]": {
"editor.defaultFormatter": "xaver.clang-format",
"editor.detectIndentation": false,
"editor.insertSpaces": false,
"editor.tabSize": 4,
"editor.formatOnSave": true
}
}
12 changes: 12 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Format Firmware Sources",
"type": "shell",
"command": "bash ${workspaceFolder}/scripts/format_cpp.sh",
"group": "build",
"problemMatcher": []
}
]
}
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,13 @@ if (rise.ok && set.ok) {
- You can also run `pio ci examples/basic_date --board esp32dev --project-option "build_flags=-std=gnu++17"` locally.
- Unity smoke tests live in `test/test_esp_date`; run them on hardware with `pio test -e esp32dev` (or your board environment) to exercise arithmetic, formatting, and parsing routines.

## Formatting Baseline

This repository follows the firmware formatting baseline from `esptoolkit-template`:
- `.clang-format` is the source of truth for C/C++/INO layout.
- `.editorconfig` enforces tabs (`tab_width = 4`), LF endings, and final newline.
- Format all tracked firmware sources with `bash scripts/format_cpp.sh`.

## License
MIT — see [LICENSE.md](LICENSE.md).

Expand Down
188 changes: 100 additions & 88 deletions examples/basic_date/basic_date.ino
Original file line number Diff line number Diff line change
Expand Up @@ -6,103 +6,115 @@ ESPDate date;
bool releasedDateResources = false;

class SyncObserver {
public:
void onNtpSync(const DateTime &syncedAtUtc) {
Serial.printf("NTP synced, epoch: %lld\n", static_cast<long long>(syncedAtUtc.epochSeconds));
}
public:
void onNtpSync(const DateTime &syncedAtUtc) {
Serial.printf(
"NTP synced, epoch: %lld\n",
static_cast<long long>(syncedAtUtc.epochSeconds)
);
}
};

SyncObserver syncObserver;

void printFormatted(const char *label, const DateTime &dt) {
char buf[32];
if (date.formatUtc(dt, ESPDateFormat::Iso8601, buf, sizeof(buf))) {
Serial.print(label);
Serial.println(buf);
}
char buf[32];
if (date.formatUtc(dt, ESPDateFormat::Iso8601, buf, sizeof(buf))) {
Serial.print(label);
Serial.println(buf);
}
}

void setup() {
Serial.begin(115200);
delay(200);
Serial.println("ESPDate basic example");
Serial.println("Connect WiFi so configTzTime can sync time, or set the system clock manually before using date.now().");
ESPDateConfig cfg{0.0f, 0.0f, "CET-1CEST,M3.5.0/2,M10.5.0/3", "pool.ntp.org", 15 * 60 * 1000};
cfg.ntpServer2 = "time.google.com";
cfg.ntpServer3 = "time.cloudflare.com";
date.init(cfg);
date.setNtpSyncCallback(std::bind(&SyncObserver::onNtpSync, &syncObserver, std::placeholders::_1));
date.setNtpSyncIntervalMs(10 * 60 * 1000); // optional runtime update
date.syncNTP(); // force an immediate refresh using configured NTP

DateTime now = date.now();
DateTime tomorrow = date.addDays(now, 1);
DateTime lastWeek = date.subDays(now, 7);
DateTime lastYear = date.subYears(now, 1);

printFormatted("Now (UTC): ", now);
printFormatted("Tomorrow (UTC): ", tomorrow);
printFormatted("Last week (UTC): ", lastWeek);

char localNowBuffer[32];
if (date.nowLocalString(localNowBuffer, sizeof(localNowBuffer))) {
Serial.printf("Now (Local string): %s\n", localNowBuffer);
}
std::string utcNowString = date.nowUtcString();
Serial.printf("Now (UTC string): %s\n", utcNowString.c_str());

char lastYearLocalBuffer[32];
if (lastYear.localString(lastYearLocalBuffer, sizeof(lastYearLocalBuffer))) {
Serial.printf("Last year (Local string): %s\n", lastYearLocalBuffer);
}

int64_t deltaDays = date.differenceInDays(tomorrow, now);
Serial.printf("Days between now and tomorrow: %lld\n", static_cast<long long>(deltaDays));

bool equalSeconds = date.isEqual(now, tomorrow);
bool equalMinutes = date.isEqualMinutes(now, date.addSeconds(now, 30)); // same minute
Serial.printf("Equal (seconds precision): %s\n", equalSeconds ? "true" : "false");
Serial.printf("Equal (minutes precision): %s\n", equalMinutes ? "true" : "false");

DateTime start = date.startOfDayLocal(now);
DateTime end = date.endOfDayLocal(now);

char buf[32];
if (date.formatLocal(start, ESPDateFormat::DateTime, buf, sizeof(buf))) {
Serial.print("Local day starts at: ");
Serial.println(buf);
}
if (date.formatLocal(end, ESPDateFormat::DateTime, buf, sizeof(buf))) {
Serial.print("Local day ends at: ");
Serial.println(buf);
}

ESPDate::ParseResult parsed = date.parseIso8601Utc("2025-12-31T23:59:30Z");
if (parsed.ok) {
Serial.print("Parsed ISO-8601 (UTC): ");
date.formatUtc(parsed.value, ESPDateFormat::DateTime, buf, sizeof(buf));
Serial.println(buf);
}

ESPDate::ParseResult parsedLocal = date.parseDateTimeLocal("2025-01-01 03:00:00");
if (parsedLocal.ok) {
Serial.print("Parsed local datetime: ");
if (date.formatLocal(parsedLocal.value, ESPDateFormat::DateTime, buf, sizeof(buf))) {
Serial.println(buf);
}
}

MoonPhaseResult phase = date.moonPhase();
if (phase.ok) {
Serial.printf("Moon phase: %d deg, illumination: %.3f\n", phase.angleDegrees, phase.illumination);
}
Serial.begin(115200);
delay(200);
Serial.println("ESPDate basic example");
Serial.println(
"Connect WiFi so configTzTime can sync time, or set the system clock manually before using "
"date.now()."
);
ESPDateConfig cfg{0.0f, 0.0f, "CET-1CEST,M3.5.0/2,M10.5.0/3", "pool.ntp.org", 15 * 60 * 1000};
cfg.ntpServer2 = "time.google.com";
cfg.ntpServer3 = "time.cloudflare.com";
date.init(cfg);
date.setNtpSyncCallback(
std::bind(&SyncObserver::onNtpSync, &syncObserver, std::placeholders::_1)
);
date.setNtpSyncIntervalMs(10 * 60 * 1000); // optional runtime update
date.syncNTP(); // force an immediate refresh using configured NTP

DateTime now = date.now();
DateTime tomorrow = date.addDays(now, 1);
DateTime lastWeek = date.subDays(now, 7);
DateTime lastYear = date.subYears(now, 1);

printFormatted("Now (UTC): ", now);
printFormatted("Tomorrow (UTC): ", tomorrow);
printFormatted("Last week (UTC): ", lastWeek);

char localNowBuffer[32];
if (date.nowLocalString(localNowBuffer, sizeof(localNowBuffer))) {
Serial.printf("Now (Local string): %s\n", localNowBuffer);
}
std::string utcNowString = date.nowUtcString();
Serial.printf("Now (UTC string): %s\n", utcNowString.c_str());

char lastYearLocalBuffer[32];
if (lastYear.localString(lastYearLocalBuffer, sizeof(lastYearLocalBuffer))) {
Serial.printf("Last year (Local string): %s\n", lastYearLocalBuffer);
}

int64_t deltaDays = date.differenceInDays(tomorrow, now);
Serial.printf("Days between now and tomorrow: %lld\n", static_cast<long long>(deltaDays));

bool equalSeconds = date.isEqual(now, tomorrow);
bool equalMinutes = date.isEqualMinutes(now, date.addSeconds(now, 30)); // same minute
Serial.printf("Equal (seconds precision): %s\n", equalSeconds ? "true" : "false");
Serial.printf("Equal (minutes precision): %s\n", equalMinutes ? "true" : "false");

DateTime start = date.startOfDayLocal(now);
DateTime end = date.endOfDayLocal(now);

char buf[32];
if (date.formatLocal(start, ESPDateFormat::DateTime, buf, sizeof(buf))) {
Serial.print("Local day starts at: ");
Serial.println(buf);
}
if (date.formatLocal(end, ESPDateFormat::DateTime, buf, sizeof(buf))) {
Serial.print("Local day ends at: ");
Serial.println(buf);
}

ESPDate::ParseResult parsed = date.parseIso8601Utc("2025-12-31T23:59:30Z");
if (parsed.ok) {
Serial.print("Parsed ISO-8601 (UTC): ");
date.formatUtc(parsed.value, ESPDateFormat::DateTime, buf, sizeof(buf));
Serial.println(buf);
}

ESPDate::ParseResult parsedLocal = date.parseDateTimeLocal("2025-01-01 03:00:00");
if (parsedLocal.ok) {
Serial.print("Parsed local datetime: ");
if (date.formatLocal(parsedLocal.value, ESPDateFormat::DateTime, buf, sizeof(buf))) {
Serial.println(buf);
}
}

MoonPhaseResult phase = date.moonPhase();
if (phase.ok) {
Serial.printf(
"Moon phase: %d deg, illumination: %.3f\n",
phase.angleDegrees,
phase.illumination
);
}
}

void loop() {
// Demonstrate explicit teardown in long-running sketches.
if (!releasedDateResources && millis() > 60000UL && date.isInitialized()) {
date.deinit();
releasedDateResources = true;
Serial.println("ESPDate deinitialized.");
}
// Demonstrate explicit teardown in long-running sketches.
if (!releasedDateResources && millis() > 60000UL && date.isInitialized()) {
date.deinit();
releasedDateResources = true;
Serial.println("ESPDate deinitialized.");
}
}
Loading