Table of Contents
Since C++20 the <chrono> header provides time zone
support via std::chrono::tzdb, a complete interface to the
IANA Time Zone Database.
The library supports converting times between UTC and the local time
in a specific time zone, querying UTC offsets and DST information for a
given time zone on a given date, and finding the system's current time zone.
Key entry points are std::chrono::get_tzdb() to access
the database, std::chrono::locate_zone() to look up
a zone by name, and std::chrono::current_zone() to obtain
the system's local time zone. The std::chrono::zoned_time
class template represents a time point in a given time zone.
The database can be reloaded at runtime
via std::chrono::reload_tzdb() to pick up updated zone
data without restarting the program.
The Time Zone Database
Parser by Howard E. Hinnant is a valuable source of information and
examples of usage for the date library, on which the standard
API is based.
When support is enabled (which is default) the time zone database class,
tzdb, uses the
zic file format, both as the format of the input
files, and for in-memory representation. This results in a reduced
memory footprint for many programs, as time zone data is stored in
a compact form until needed by the application.
The transitions to/from DST (Daylight Savings Time) are generated and
cached on the fly, when information
for a given zone is requested. Consequently, the initial request for
UTC conversion for a given time_zone object is slower.
To mitigate this, a time_zone can be expanded for a
particular time range at the start
of the program (and after each reload), by iterating over the
sys_info dequence, as in the example below.
void
expand_zone(const std::chrono::time_zone* tz,
const std::chrono::sys_seconds start,
const std::chrono::sys_seconds end)
{
std::chrono::sys_seconds time = start;
while (time < end) {
const std::chrono::sys_info& info = tz->get_info(time);
time = info.end + std::chrono::seconds(1);
}
}
The time_zone pointer returned from
std::chrono::current_zone, is determined by looking up
(by std::chrono::locate_zone) IANA zone name
determined as follows (for platforms other than AIX and Windows):
readlink, suffix components of the
path of the file linked by /etc/localtime. The suffixes
are matched in the other of increasing length, starting from the final
component, until the match is found in the time zone database.
Any repeated slashes (//) are eliminated.
/etc/timezone (Debian
derivatives) or /var/db/zoneinfo (FreeBSD) in that order.
TIMEZONE and ZONE keys in
/etc/sysconfig/clock.
UTC.
For AIX, the value of TZ environment value is used, with
fallback to UTC.
On Windows TimeZoneKeyName is mapped to the IANA zone, using
internal mapping hard-coded in library, with fallback to UTC:
TimeZoneKeyName is empty or DST is disabled,
then a value based on Bias is returned: Etc/UTC
for zero, Etc/GMT+/-N for value that is multiply of 60,
and empty string otherwise.
TimeZoneKeyName,
that name is used.
TimeZoneKeyName corresponds to multiple IANA zones,
a 2-letter country code is used, retrieved by applying GetGeoInfoW
on the result of GetUserGeoID(GEOCLASS_NATION).
If determining the country code fails, or no entry exits for a given code,
the first mapping entry (001) is returned.
Depending on the build configuration (see
--with-libstdcxx-zoneinfo= documentation in
Configuring).
the content of the time zone database is sourced from
tzdata.zi and leapseconds files
located in zoneinfo_dir, or from static information
embedded in the library.
By default, zoneinfo_dir is set to the
system-specific default directory (if a suitable dir is known for target),
usually /usr/share/zoneinfo. If no such directory
exits, or it does not contain the required files in the correct format,
embedded static data is used as fallback.
In addition to --with-libstdcxx-zoneinfo= configure option
(used during GCC build), the path of zoneinfo_dir
can be overridden by the application by providing a definition of the
__gnu_cxx::zoneinfo_dir_overrride() function. The
returned path should be directory that contains tzdata.zi
and leapseconds files in the
zic format.
The embedded timezone information corresponds to the copy of the
IANA database at the time of the release, and its version can be queried
using std::chrono::get_tzdb().version.
The full time zone database can be disabled when GCC is configured,
in which case a minimal time zone database is provided. This minimal
database can be identified by chrono::get_tzdb().version
being set to "ersatz". This database contains leapseconds
data, Etc/UTC, Etc/GMT zones, and their aliases
(Etc/Zulu, Etc/UCT, Etc/Universal,
Etc/Greenwich, Etc/GMT0, Etc/GMT+0,
Etc/GMT-0). This information is sufficient to support
conversion between utc_clock and sys_clock,
as well as a UTC fallback for current_zone
(current zone).
The std::chrono::reload_tzdb() function may be used to load
updated content of the tzdata.zi and leapseconds
files from zoneinfo_dir (if
data sources). If the version is different
(std::chrono::remote_version() != std::chrono::get_tzdb().version),
a new element is added at the front of the tzdb_list.
This new database is used for subsequent calls to current_zone
and locate_zone.
The above process is thread-safe, and does not invalidate nor change any
pre-existing pointers to time_zone objects. However, for a
long-running application it may lead to accumulation of time zone data,
and thus increased memory usage. In most cases, this is acceptable even
for long-running applications, due to infrequent updates to the IANA database,
and the reduced footprint of the libstdc++ implementation (see
tzdb uses zic format).
If accumulating old tzdb databases is not acceptable
(e.g. due to memory constraints), entries
may be removed from tzdb_list using tzdb_list::erase_after.
When using this function, you are responsible for ensuring that the application
no longer is no longer using any time_zone* to a removed database.
If you erase a tzdb while some part of the application is still
using it (or one of its time_zone objects) you will create
a dangling pointer, leading to undefined behaviour.
To make tzdb_list::erase_after safer, libstdc++ provides an
extension that avoids creating dangling pointers.
The tzdb_list uses shared_ptr<tzdb> to
refer to each entry in the list, and tzdb_list::const_iterator
also uses a shared_ptr<tzdb> to refer to its target.
This means that iterators into the list share ownership of the list elements,
so that erasing an element from the list does not destroy it if there are
any iterators which share ownership of the element. The application can
use this to ensure that tzdb and time_zone
objects are not destroyed while they're still being used. The lifetime
of a tzdb that is still in use can be extended by holding
onto a tzdb_list::const_iterator that refers to it.