diff --git a/Sming/Core/SystemClock.cpp b/Sming/Core/SystemClock.cpp index e109a3aa43..c8686823a6 100644 --- a/Sming/Core/SystemClock.cpp +++ b/Sming/Core/SystemClock.cpp @@ -19,6 +19,9 @@ time_t SystemClockClass::now(TimeZone timeType) const uint32_t systemTime = RTC.getRtcSeconds(); if(timeType == eTZ_Local) { + if(checkTimeZoneOffset) { + checkTimeZoneOffset(systemTime); + } systemTime += timeZoneOffsetSecs; } diff --git a/Sming/Core/SystemClock.h b/Sming/Core/SystemClock.h index 1a79fa49c7..d5898160c1 100644 --- a/Sming/Core/SystemClock.h +++ b/Sming/Core/SystemClock.h @@ -34,6 +34,23 @@ enum TimeZone { class SystemClockClass { public: + /** + * @brief Optional callback which can be used to automate handling of timezone changes + * @param systemTime The current system time + * + * Although the timezone offset only changes twice a year, when it does change the + * corresponding offset should be updated promptly. + * + * `setTimeZoneOffset()` is typically called when updating via real-time clock chip or NTP, + * but this can still leave a considerable gap during which the local time will be wrong. + * + * The callback should check the provided systemTime and determine if the time zone offset + * requires changing and, if so, call `setTimeZoneOffset()`. + * One way to make this efficient is to cache the `time_t` value for the *next* change, + * rather than attemtpting to compute the offset on every call. + */ + using CheckTimeZoneOffset = Delegate; + /** @brief Get the current date and time * @param timeType Time zone to use (UTC / local) * @retval DateTime Current date and time @@ -88,7 +105,17 @@ class SystemClockClass return timeSet; } + /** + * @brief Set/clear a callback which is invoked whenever local time is queried. + * @note This callback is *only* invoked when `now()` is called. + */ + void onCheckTimeZoneOffset(CheckTimeZoneOffset callback) + { + checkTimeZoneOffset = callback; + } + private: + CheckTimeZoneOffset checkTimeZoneOffset; int timeZoneOffsetSecs = 0; bool timeSet = false; }; diff --git a/Sming/Libraries/Timezone b/Sming/Libraries/Timezone index 9af032b5b5..7263e848e0 160000 --- a/Sming/Libraries/Timezone +++ b/Sming/Libraries/Timezone @@ -1 +1 @@ -Subproject commit 9af032b5b5820434f94c715a39da787b7ab8301b +Subproject commit 7263e848e07a3d54c32811baf6d37b03186afe5f diff --git a/samples/SystemClock_NTP/app/NtpClientDemo.cpp b/samples/SystemClock_NTP/app/NtpClientDemo.cpp index 8fb026c185..d79b862209 100644 --- a/samples/SystemClock_NTP/app/NtpClientDemo.cpp +++ b/samples/SystemClock_NTP/app/NtpClientDemo.cpp @@ -38,6 +38,21 @@ time_t getNextSunriseSet(bool isSunrise) return t; } +void checkTimeZoneOffset(time_t systemTime) +{ + static time_t nextChange; + static const TimeChangeRule* rule; + + if(!rule) { + tz.toLocal(systemTime, &rule); + } else if(systemTime < nextChange) { + return; + } + + SystemClock.setTimeZoneOffset(rule->offset * SECS_PER_MIN); + nextChange = tz.getNextChange(systemTime, &rule); +} + } // namespace void NtpClientDemo::ntpResult(NtpClient& client, time_t ntpTime) @@ -46,9 +61,10 @@ void NtpClientDemo::ntpResult(NtpClient& client, time_t ntpTime) * Update the system clock and calculate the correct time offset, * accounting for time zone and daylight savings. */ - auto localTime = tz.toLocal(ntpTime); SystemClock.setTime(ntpTime, eTZ_UTC); - SystemClock.setTimeZoneOffset(localTime - ntpTime); + + // Now we've set the clock, we can determine the initial active timezone and maintain the offset + SystemClock.onCheckTimeZoneOffset(checkTimeZoneOffset); /* * Display the new time @@ -64,4 +80,21 @@ void NtpClientDemo::ntpResult(NtpClient& client, time_t ntpTime) DateTime sunset = getNextSunriseSet(false); Serial << _F("Next sunrise at ") << sunrise.toShortTimeString() << _F(", sunset at ") << sunset.toShortTimeString() << endl; + + /* + * Display points at which daylight savings changes. + */ + DateTime dt(ntpTime); + + auto& dstRule = tz.getRule(true); + DateTime dstDateTime = dstRule(dt.Year); + Serial << dstRule.tag << _F(" starts ") << dstDateTime.toHTTPDate() << endl; + + auto& stdRule = tz.getRule(false); + DateTime stdDateTime = stdRule(dt.Year); + Serial << stdRule.tag << _F(" starts ") << stdDateTime.toHTTPDate() << endl; + + const TimeChangeRule* rule; + time_t nextChange = tz.getNextChange(dt, &rule); + Serial << _F("Next change to ") << rule->tag << _F(" is ") << DateTime(nextChange).toHTTPDate() << endl; }