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.cpp and tzdata.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.

See https://docs.python.org/3/library/zoneinfo.html.

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

Typedefs

using ZoneList = FSTR::Vector<Info>
using AreaMap = FSTR::Map<FSTR::String, ZoneList>

Enums

enum week_t

Week number for Rule

Values:

enumerator First
enumerator Second
enumerator Third
enumerator Fourth
enumerator Last
enum dow_t

Day of week. Same as DateTime dtDays_t.

Values:

enumerator Sun
enumerator Mon
enumerator Tue
enumerator Wed
enumerator Thu
enumerator Fri
enumerator Sat
enum month_t

Month by name. Same as DateTime dtMonth_t.

Values:

enumerator Jan
enumerator Feb
enumerator Mar
enumerator Apr
enumerator May
enumerator Jun
enumerator Jul
enumerator Aug
enumerator Sep
enumerator Oct
enumerator Nov
enumerator Dec

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:

    https://sourceware.org/glibc/manual/2.39/html_node/TZ-Variable.html
Original versions of this library only allowed a single value for hours, for example:
    {"BST", Last, Sun, Mar, 1, 60}
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:
    {"+1245", First, Sun, Apr, 3.75, 765}
Western greenland has a negative hours value, America/Godthab:
{"-01", Last, Sun, Mar, -1, -60}
Note that at time of writing newlib (the embedded C library) does not support negative time values (via tzset) and produces incorrect results.

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.

Public Members

Tag tag

e.g. DST, UTC, etc.

int16_t offsetMins

Offset from UTC.

struct Time
#include <Timezone.h>
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

Parameters:
  • dstStartRule giving start of daylight savings

  • stdStartRule giving start of 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:

stdRule 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.

struct Transition
#include <tzdb.h>
struct Info
#include <tzdb.h>

References

Used by

Environment Variables

SoC support

  • esp32

  • esp32c2

  • esp32c3

  • esp32s2

  • esp32s3

  • esp8266

  • host

  • rp2040

  • rp2350