Timezone
Sming library to support local/UTC time conversions using rules
See SystemClock NTP for example usage.
Port of https://github.com/JChristensen/Timezone for Sming.
Rules database
By default, a set of rules for all the main IANA timezones is created in the applications out/Timezone directory.
This can be access via #include <tzdata.h>.
Customise using the following configuration variables:
- APP_TZDATA_DIR
Default: $(PROJECT_DIR)/out/Timezone
Location for
tzdata.cppandtzdata.h. Set to empty string if you don’t want these to be built.
- APP_TZDATA_OPTS
Override to customise generated data. Typically this is only required for testing to include transition tables.
These files are generated using a python script tools/compile.py.
Note
The script uses the system IANA timezone database.
For development systems without one installed (Windows), the tzdata package is required.
Advanced usage
You can use the tool to lookup and generate the appropriate rules for a timezone, by name.
Note
You can use tzselect to locate the appropriate timezone names.
If you only need a few timezone definitions then this may suffice:
python tools/compile.py Europe/London
will produce the following output:
/*
* Source: /usr/share/zoneinfo
* Version: 2024a
*/
namespace London {
DEFINE_FSTR(location, "London")
constexpr const TimeChangeRule std PROGMEM {"GMT", Last, Sun, Oct, 2, 0};
constexpr const TimeChangeRule dst PROGMEM {"BST", Last, Sun, Mar, 1, 60};
constexpr const Info info PROGMEM {std, dst};
}
If you want the whole database:
python tools/posix.py --all --output files
This will create tzdata.h and tzdata.cpp in the files directory (which must exist).
The files contain a lookup table (TZ::areas) which can be used to lookup timezones by name.
The library test application uses this during build - see the component.mk file.
Timezones are identified by area/location. For example:
Zone name Area Location Europe/London Europe London America/Argentina/Mendoza America Argentina/Mendoza
The compiler produces one table for each area, such as TZ::Europe::zones.
These are collected in the TZ::areas map.
For ease of use, you can use this data directly:
#include "tzdata.h"
void foo()
{
Timezone tz(TZ::Europe::London::info);
time_t nowUtc = SystemClock.now(eTZ_UTC);
time_t nowLocal = tz.toLocal(now);
}
Additional information can be included in the generated Info structure using the following options:
–names Include the zone area and location
–tzstr Include the POSIX timezone strings
–transitions Include transition times
See the test application for example usage.
The database can be output with vary levels of verbosity, depending on requirements. Compiled for esp8266 gives these results: there are 488 zones in the source data:
Option size increase
irom0_attr total per zone
============== =========== =========================
empty table 74984
just rules 91668 16684 34.2
--name 95588 3920 8.03
--tzstr 99408 3820 7.83
--transitions 107252 7844 16.07
Note
The IANA timezone database is updated regularly. Many zones are stable and the POSIX strings do not change.
Applications should generally provide a mechanism for updating rules as required, for example using an on-disk database. This is beyond the scope of this library.
Testing
The test application in this library builds the timezone table as describe above and identifies the transition times to/from daylight savings for each zone.
Most zones do not use daylight savings and this is also checked for. In this case the DST rule is just a reference to the STD rule.
Only conversions from UTC to local time are checked: it’s impossible to go the other way reliably since when jumping forward there’s a gap where local time isn’t valid, and when jumping back there’s a repeated hour.
This UTC-to-local conversion is checked against the standard C library routines which interpret the POSIX strings directly.
Note
At time of writing (May 2024) this check fails for several timezones when tested on an ESP8266 using gcc 10.2. The zones are:
America/Godthab <-02>2<-01>,M3.5.0/-1,M10.5.0/0 America/Nuuk <-02>2<-01>,M3.5.0/-1,M10.5.0/0 America/Scoresbysund <-02>2<-01>,M3.5.0/-1,M10.5.0/0
These are the only zones with a negative TIME component, so clearly the newlib implementation cannot handle it. However, the glibc version can (as well as this library).
A second check is made using the timezone name itself (e.g. Europe/London).
This is the “most correct” result available since it uses the full IANA database
information which cannot be expressed by a POSIX timezone string.
These are highlighted in the output for information purposes.
Further information:
API Documentation
-
namespace TZ
-
Enums
-
enum week_t
Week number for
RuleValues:
-
enumerator First
-
enumerator Second
-
enumerator Third
-
enumerator Fourth
-
enumerator Last
-
enumerator First
Functions
-
const Info *findZone(const String &name)
Find a zone given its full name.
This makes things a bit easier with little risk of false-positives.
- Parameters:
name – Full name of zone
- Return values:
Info* – Zone information, if found Comparison is performed on full name without case-sensitivity and with all punctuation removed. Thus: “europe-london” matches “Europe/London” “africa/porto_novo” matches “Africa/Porto-Novo” “america port au prince” matches “America/Port-au-Prince”
Variables
-
static constexpr time_t minTime = std::max(-5364662400LL, (long long)std::numeric_limits<time_t>::min() + 1)
Earliest timestamp we might wish to use.
- Todo:
Simplify this expression once 32-bit time_t has been eradicated.
Note
64-bit -5364662400 “1800-01-01 00:00:00” 32-bit -2147483647 “1901-12-31 20:45:53”
-
static constexpr time_t maxTime = std::min(253402300799LL, (long long)std::numeric_limits<time_t>::max() - 1)
Largest future timestamp value we could reasonably want.
- Todo:
Simplify this expression once 32-bit time_t has been eradicated.
Note
64-bit 253402300799 “9999-12-31 23:59:59” 32-bit 2147483646 “2038-01-19 03:14:06”
-
static constexpr time_t invalidTime = maxTime + 1
Value outside normal range used to indicate abnormal or uninitialised time values.
- static constexpr const Rule PROGMEM rule_none = {}
-
struct Rule
- #include <Timezone.h>
Describe rules for when daylight/summer time begins, and when standard time begins.
This rule structure is an exact analogue of the POSIX ‘M’-style rules, which are the only ones in general use. The GLIBC manual page provides a good overview of these rules:
Original versions of this library only allowed a single value for hours, for example:https://sourceware.org/glibc/manual/2.39/html_node/TZ-Variable.html
However, some zones also require a minute value, such as Pacific/Chatham which changes at 03:45. We can use a fractional value (3.75) for this:{"BST", Last, Sun, Mar, 1, 60}
Western greenland has a negative hours value, America/Godthab:{"+1245", First, Sun, Apr, 3.75, 765}
Note that at time of writing newlib (the embedded C library) does not support negative time values (via tzset) and produces incorrect results.{"-01", Last, Sun, Mar, -1, -60}
Public Functions
-
time_t operator()(unsigned year) const
Convert the given time change rule to a time_t value for the given year.
- Parameters:
year – Which year to work with
- Return values:
time_t – Timestamp for the given time change
-
inline explicit operator int() const
Obtain a numeric value for comparison purposes.
-
struct Time
- #include <Timezone.h>
-
time_t operator()(unsigned year) const
-
class Timezone
- #include <Timezone.h>
Class to support local/UTC time conversions using rules.
Public Functions
-
inline Timezone(const Rule &dstStart, const Rule &stdStart)
Create a timezone with daylight savings.
Note
If both rules are the same then the zone operates in permanent standard time
-
inline explicit Timezone(const Rule &std)
Create a timezone which has no daylight savings.
Note
Only tag and offset fields from rule are significant
- Parameters:
std – Rule describing standard time
-
inline void init(const Rule &dstStart, const Rule &stdStart)
@deprected Use copy assignment, e.g.
tz = Timezone(...)
-
time_t toLocal(time_t utc, const Rule **rule = nullptr)
Convert the given UTC time to local time, standard or daylight time.
- Parameters:
utc – Time in UTC
rule – Optionally return the rule used to convert the time
- Return values:
time_t – The local time
-
ZonedTime makeZoned(time_t utc, bool beforeTransition = false)
Obtain a ZonedTime instance for the given UTC.
- Parameters:
utc – Time in UTC
beforeTransition – If time is exactly on a transition to/from daylight savings then this determines whether the returned information contains the local time prior to the change or after the change.
- Return values:
ZonedTime – Contains the UTC time given plus offset, etc. at that point in time
-
ZonedTime toUTC(time_t local)
Convert the given local time to UTC time.
WARNING: This function is provided for completeness, but should seldom be needed and should be used sparingly and carefully.
Ambiguous situations occur after the Standard-to-DST and the DST-to-Standard time transitions. When changing to DST, there is one hour of local time that does not exist, since the clock moves forward one hour. Similarly, when changing to standard time, there is one hour of local times that occur twice since the clock moves back one hour.
This function does not test whether it is passed an erroneous time value during the Local -> DST transition that does not exist. If passed such a time, an incorrect UTC time value will be returned.
If passed a local time value during the DST -> Local transition that occurs twice, it will be treated as the earlier time, i.e. the time that occurs before the transition.
Calling this function with local times during a transition interval should be avoided.
Note
- Parameters:
local – Local time
- Return values:
ZonedTime – Contains UTC point in time with associated local offset, etc.
-
bool utcIsDST(time_t utc)
Determine whether the UTC time is within the DST interval or the Standard time interval.
- Parameters:
utc –
- Return values:
bool – true if time is within DST
-
bool locIsDST(time_t local)
Determine whether the given local time is within the DST interval or the Standard time interval.
- Parameters:
local – Local time
- Return values:
bool – true if time is within DST
-
inline const char *timeTag(bool isDst) const
Return the appropriate dalight-savings tag to append to displayed times.
- Parameters:
isDst – true if DST tag is required, otherwise non-DST tag is returned
- Return values:
const – char* The tag
-
inline const char *utcTimeTag(time_t utc)
Return the appropriate time tag for a UTC time.
- Parameters:
utc – The time in UTC
- Return values:
const – char* Tag, such as UTC, BST, etc.
-
inline const char *localTimeTag(time_t local)
Return the appropriate time tag for a local time.
- Parameters:
local – The local time
- Return values:
const – char* Tag, such as UTC, BST, etc.
-
ZonedTime getNextChange(time_t utcFrom)
Determine when the next change to/from DST is.
- Parameters:
utcFrom – Point in time after which change is to occur
- Return values:
ZonedTime – UTC time When the change will occur, or maxTime if there is no DST in effect. ZonedTime::local() returns the new local time at the transition.
-
ZonedTime getTransition(uint16_t year, bool toDst)
Get transition time for the given year.
- Parameters:
year –
toDst – true to obtain STD->DST transition, false for DST->STD
- Return values:
ZonedTime – Time of transition, or maxTime if there is no DST in effect. ZonedTime::local() returns the new local time at the transition.
-
inline const Rule &getRule(bool isDst) const
Get reference to a timechange rule.
- Parameters:
isDst – true for DST rule, false for STD rule
- Return values:
Rule –
-
inline bool hasDaylightSavings() const
If dst and std rules are the same we do not use daylight savings.
-
inline Timezone(const Rule &dstStart, const Rule &stdStart)
-
struct Transition
- #include <tzdb.h>
-
struct Info
- #include <tzdb.h>
-
enum week_t
References
Used by
SystemClock NTP Sample
Environment Variables
SoC support
esp32
esp32c2
esp32c3
esp32s2
esp32s3
esp8266
host
rp2040
rp2350