Chapter 13.  Time

Table of Contents

Time zone database
tzdb uses zic format
current zone
data sources
tzdb_list::const_iterator extends lifetime

Time zone database

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.

tzdb uses zic format

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);
       }
     }
   

current zone

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):

  • On system supporting 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.
  • Names stored in the files /etc/timezone (Debian derivatives) or /var/db/zoneinfo (FreeBSD) in that order.
  • Values for 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:

  • If the returned 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.
  • If the mapping contains a single entry for TimeZoneKeyName, that name is used.
  • If 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.

data sources

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

tzdb_list::const_iterator extends lifetime

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.