Howard E. Hinnant
2016-05-15
Creative
Commons License
This work is licensed under a Creative Commons Attribution 4.0 International License.

Time Zone Database Parser

Contents

Introduction

I had just completed writing date, which is a library for extending <chrono> into the realm of calendars, and I was looking around for the most challenging date time problem I could find with which I could demonstrate the power of this new library. "I know," I said to myself, "I'll handle all of the world's time zones, and maybe even leap seconds!" Thus began my journey into a rabbit hole which I knew existed, but had never truly appreciated the intricacies of.

This library adds timezone and leap second support to this date library. This is a separate library from date because many clients of date do not need timezone nor leap second support, and this support does not come for free (though the cost is quite reasonable).

This library is a complete parser of the IANA Time Zone Database. This database contains timezone information that represents the history of local time for many representative locations around the globe. It is updated every few months to reflect changes made by political bodies to time zone boundaries, UTC offsets, and daylight-saving rules. The database also maintains a list of leap seconds from 1972 through the present.

The IANA Time Zone Database contains four specific types of data:

  1. Zone: A geographic location with a human-readable name (e.g. "America/New_York") which specifies the offset from UTC and an abbreviation for the zone. This data includes daylight saving rules, if applicable, for the zone. This data is not only the rules currently in effect for the region, but also includes specifications dating back to at least 1970, and in most cases dating back to the mid 1800's (when uniform time was first introduced across regions larger than individual towns and cities).

  2. Rule: A specification for a single daylight-saving rule. This helps implement and consolidate the specifications of Zones.

  3. Link: This is an alternative name for a Zone.

  4. Leap: The date of the insertion of a leap second.

The library documented herein provides access to all of this data, and offers efficient and convenient ways to compute with it. And this is all done based on the date library, which in turn is based on the C++11/14 <chrono> library. So once you've learned those fundamental libraries, the learning curve for this library is greatly eased.

Description

Everything documented below is in namespace date. Explicit references to this namespace in example code below is intentionally omitted in the hopes of reducing verbosity.

What is the current local time?

One of the first things people want to is find out what current local time it is. Here is a complete program to print out the local time in human readable format:

#include "tz.h"
#include <iostream>

int
main()
{
    using namespace date;
    using namespace std::chrono;
    auto local_time = make_zoned(current_zone(), system_clock::now());
    std::cout << local_time << '\n';
}

This just output for me:

2016-05-14 18:33:24.205124 EDT

There are some noteworthy points about this program:

Everything about the above program can be customized: the precision, the formatting, and the time zone. But by default, things just work, and don't throw away information.

For example let's say we wanted to limit the precision to milliseconds. This can be done by inserting floor<milliseconds> in one place. This makes local_time have just a precision of milliseconds and that is reflected in the streaming operator with no further effort:

auto local_time = make_zoned(current_zone(), floor<milliseconds>(system_clock::now()));
std::cout << local_time << '\n';  // 2016-05-14 18:33:24.205 EDT

Seconds precision is just as easy:

auto local_time = make_zoned(current_zone(), floor<seconds>(system_clock::now()));
std::cout << local_time << '\n';  // 2016-05-14 18:33:24 EDT

The entire strftime / time_put formatting capability is also at your fingertips (and at any precision):

auto local_time = make_zoned(current_zone(), system_clock::now());
std::cout << format("%a, %b %d, %Y at %I:%M %p %Z", local_time) << '\n';
// Sat, May 14, 2016 at 06:33 PM EDT

Using any std::locale your OS supports:

auto local_time = make_zoned(current_zone(), floor<seconds>(system_clock::now()));
std::cout << format(locale("de_DE"), "%a, %b %d, %Y at %T %Z", local_time) << '\n';
// Sa, Mai 14, 2016 at 18:33:24 EDT

What time is it somewhere else in the world?

From the previous section:

Hmm... German locale in an American time zone.

We can fix that easily too:

auto zone = locate_zone("Europe/Berlin");
auto local_time = make_zoned(zone, floor<seconds>(system_clock::now()));
std::cout << format(locale("de_DE"), "%a, %b %d, %Y at %T %Z", local_time) << '\n';
// So, Mai 15, 2016 at 00:33:24 CEST

The date::locate_zone() function looks up the IANA time zone with the name "Europe/Berlin" and returns a const time_zone* which has no ownership issues and can be freely and cheaply copied around. It is not possible for locate_zone() to return nullptr, though it might throw an exception if pushed far enough (e.g. locate_zone("Disney/Mickey_Mouse")).

You can also call make_zoned with the time zone name right in the call:

auto local_time = make_zoned("Europe/Berlin", floor<seconds>(system_clock::now()));

The first way is very slightly more efficient if you plan on using zone multiple times since it then only has to be looked up once.

How do I convert a time_zone from one time zone to another?

So far we've only looked at converting from system_clock::now() to a local, or specific time zone. We've used make_zoned with the first argument being either current_zone() or a specification for some other time zone, and the second argument being a system_clock::time_point. So far so good.

But now I have a video-conference meeting on the first Monday of May, 2016 at 9am New York time. I need to communicate that meeting with partners in London and Sydney. And the computation is taking place on a computer in New Zealand (or some other unrelated time zone). What does that look like?

#include "tz.h"
#include <iostream>

int
main()
{
    using namespace date::literals;
    using namespace std::chrono_literals;
    auto meet_nyc = make_zoned("America/New_York", date::local_days{mon[1]/may/2016} + 9h);
    auto meet_lon = make_zoned("Europe/London",    meet_nyc);
    auto meet_syd = make_zoned("Australia/Sydney", meet_nyc);
    std::cout << "The New York meeting is " << meet_nyc << '\n';
    std::cout << "The London   meeting is " << meet_lon << '\n';
    std::cout << "The Sydney   meeting is " << meet_syd << '\n';
}

The output is the following. But before you forward it, send a generous bonus to the guys in Australia.

The New York meeting is 2016-05-02 09:00:00 EDT
The London   meeting is 2016-05-02 14:00:00 BST
The Sydney   meeting is 2016-05-02 23:00:00 AEST

Summary: zoned_time is a pairing of local or UTC time with a time_zone. The result is a well-specified point in time. And it carries with it the ability to serve as a translator to any other time_point which carries time zone information (to any precision).

The Database

The database is represented with the type TZ_DB:

struct TZ_DB
{
    std::string       version;
    std::vector<Zone> zones;
    std::vector<Link> links;
    std::vector<Leap> leaps;
    std::vector<Rule> rules;
};

This is a singleton class. You can get a const TZ_DB& to the singleton using this function:

const TZ_DB& get_tzdb();

The first call to get_tzdb() will initialize the database from your local copy of the IANA Time Zone Database located at install (a file-scope variable of type std::string in tz.cpp). You will need to catch the return of this function by const& as the TZ_DB is not constructible from a const TZ_DB. This can be done with the following example code:

auto& db = get_tzdb();

With a reference to the database in hand, you have read-only access to the entire database, which is nothing more than sorted vectors for the four types of data contained in the database. With such a reference you could (for example) print the names of all the Zones in the database:

for (auto& z : db.zones)
    std::cout << z.name() << '\n';

There are currently 377 zones in the database.

Or you could output the 89 Links, including their name() and target():

for (auto& link : db.links)
    std::cout << link << '\n';

If you aren't happy with the format this outputs in, Link has public member functions name() and target() so that you can achieve whatever format you desire.

If needed, db.version is a std::string containing the IANA Time Zone Database version of the database you are reading. For example the current version when this sentence was written was "2016a".

You can even print the entire database out in a semi-human-readable format if desired:

std::cout << db << '\n';

If you constrain the geography or history of the database during installation, those constraints will be reflected in these examples.

If you decide you need to reload the database say, because you want to install a new version of the IANA Time Zone Database without stopping your program, you can use this function:

const TZ_DB& reload_tzdb();

This re-initializes the database by reading from the install location you customized on installation. The use of the reload_tzdb function is not pain-free, and not for every application (not for most of them I'm guessing). For example see the Thread Safety section for issues related to the use of these functions.

The remote API

The remote API is enabled only if HAS_REMOTE_API is set to 1 during compilation. See Installation for more details.

std::string remote_version();

This function will query the IANA Time Zone Database website for the latest version number of the IANA database, and return it as a std::string. If an internet connection can not be made, an empty string is returned. This string can be compared against the version of your local copy of the database: get_tzdb().version.

bool remote_download(const std::string& version);

This function will attempt to download the database with the version version from the IANA Time Zone Database website. If successful, true is returned and a file named version + ".tar.gz" will be stored at the location install. If not successful, false is returned.

bool remote_install(const std::string& version);

This function will attempt to uncompress the tar file downloaded by remote_download(version) and replace any existing database with the result. It will then delete the tar file. If the tar file doesn't exist, remote_install will do nothing. Returns true on success, else returns false.

Zone

The Zone class is the most important type in this library. It provides the main access to the functionality provided by this library. Each Zone is named, represents a geographic area, and provides a mapping between UTC and the local time, in both directions. This mapping from local time to UTC is in general not one to one. The mapping, and even the specific rule, depends upon the input time_point, which can represent either UTC or local time.

The detailed API of the Zone class depends upon a small amount of infrastructure which is introduced first.

Infrastructure

using second_point = std::chrono::time_point<std::chrono::system_clock,
                                             std::chrono::seconds>;

second_point is a std::chrono::time_point based on system_clock but with the precision of seconds. This library will interoperate with system_clock::time_points of any precision. However the data in the database is largely based on second_point, and some of the data which is presented, such as that in the sys_info class, uses this type alias as a convenience, and to reduce verbosity. second_point will implicitly convert to system_clock::time_point. And coarser time_points such as the day_point from the date library will implicitly convert to second_point.

struct sys_info
{
    second_point         begin;
    second_point         end;
    std::chrono::seconds offset;
    std::chrono::minutes save;
    std::string          abbrev;
};

The sys_info struct is the return type of the get_info member function of the Zone class. It contains very detailed information about the Zone at the time_point (UTC or local) input into this member function. sys_info contains no pointers or references into the database. Therefore clients do not need to be concerned about holding on to sys_infos during a call to reload_tzdb(). Though a call to reload_tzdb() could potentially make the data in an outstanding sys_info obsolete. See Zone::get_info for more details.

enum class tz {utc, local};
enum class choose {earliest, latest};

These enums are used as input to some of the Zone member functions. tz::utc indicates that a time_point represents a time in the UTC time zone. tz::local indicates that a time_point represents a time in the Zone's local time zone. The choose enum allows a client to specify how a mapping from local to UTC should behave when the mapping is not one to one. Alternatively one can not specify a policy in the mapping, and if the mapping is not unique, an exception will be thrown.

class nonexistent_local_time
    : public std::runtime_error
{
public:
    const char* what() const override;
};

class ambiguous_local_time
    : public std::runtime_error
{
public:
    const char* what() const override;
};

These are the exception classes thrown by the local to UTC mapping. In addition to their type indicating the nature of the exceptional circumstance, they also sport a what() member function that will contain a very detailed explanation including specific times for the specific time_points involved in the attempted mapping.

If in a call to Zone::to_sys the local time_point falls into a "gap" for which no local time exists, a nonexistent_local_time exception is thrown.

If in a call to Zone::to_sys the local time_point has an ambiguous mapping to UTC, a ambiguous_local_time exception is thrown.

Either exceptional situation can be circumvented with the use of choose::earliest or choose::latest in the call to to_sys.

Zone continued

class Zone
{
public:
    const std::string& name() const;

    template <class Rep, class Period>
    std::pair
    <
        std::chrono::time_point<std::chrono::system_clock,
            typename std::common_type<std::chrono::duration<Rep, Period>,
                                      std::chrono::seconds>::type>,
        std::string
    >
    to_local(std::chrono::time_point<std::chrono::system_clock,
                                     std::chrono::duration<Rep, Period>> tp) const;

    template <class Rep, class Period>
    std::chrono::time_point<std::chrono::system_clock,
        typename std::common_type<std::chrono::duration<Rep, Period>,
                                  std::chrono::seconds>::type>
    to_sys(std::chrono::time_point<std::chrono::system_clock,
                                   std::chrono::duration<Rep, Period>> tp) const;

    template <class Rep, class Period>
    std::chrono::time_point<std::chrono::system_clock,
        typename std::common_type<std::chrono::duration<Rep, Period>,
                                  std::chrono::seconds>::type>
    to_sys(std::chrono::time_point<std::chrono::system_clock,
                                   std::chrono::duration<Rep, Period>> tp,
           choose z) const;

    template <class Rep, class Period>
    sys_info
    get_info(std::chrono::time_point<std::chrono::system_clock,
                                     std::chrono::duration<Rep, Period>> tp,
             tz timezone) const;
};

const Zone* locate_zone(const std::string& tz_name);
const Zone* current_zone();

bool operator==(const Zone& x, const Zone& y);
bool operator!=(const Zone& x, const Zone& y);
bool operator< (const Zone& x, const Zone& y);
bool operator> (const Zone& x, const Zone& y);
bool operator<=(const Zone& x, const Zone& y);
bool operator>=(const Zone& x, const Zone& y);

std::ostream& operator<<(std::ostream& os, const Zone& z);

The entire public API of the Zone is const. Once the database is initialized (or reloaded), Zones are set in concrete.


The current time zone associated with your computer can be retrieved with the namespace scope function current_zone(). For example:

std::cout << current_zone()->name() << '\n';

For me the above currently outputs America/New_York.


const Zone* locate_zone(const std::string& tz_name);

locate_zone returns a pointer to a Zone in the database associated with tz_name. If it can't find a Zone named tz_name, the implementation will search for a Link named tz_name, and then return the Zone associated with the Link's target(). If tz_name can not be found in the database, a std::runtime_error is thrown.

Example:

try
{
    cout << locate_zone("Europe/London")->name() << '\n';     // A Zone
    cout << locate_zone("Europe/Jersey")->name() << '\n';     // A Link to a Zone
    cout << locate_zone("Europe/New_Jersey")->name() << '\n'; // Doesn't exist
}
catch (const exception& e)
{
    cout << e.what() << '\n';
}

Which outputs:

Europe/London
Europe/London
Europe/New_Jersey not found in timezone database

Note that locate_zone never returns nullptr. Also note that the first call to locate_zone may implicitly initialize the database.


template <class Rep, class Period>
std::pair
<
    std::chrono::time_point<std::chrono::system_clock,
        typename std::common_type<std::chrono::duration<Rep, Period>,
                                  std::chrono::seconds>::type>,
    std::string
>
to_local(std::chrono::time_point<std::chrono::system_clock,
                                 std::chrono::duration<Rep, Period>> tp) const;

to_local maps a system_clock-associated time_point from UTC to local time, returning both the mapped time_point and an abbreviation for the local time zone. This member function accepts any precision time_point, but returns a time_point with a precision of seconds or finer. This is done because it is possible that some of the mappings returned by the database need the precision of a second.

There are only two ways this function can fail:

  1. Out of memory error. Not bloody likely. The only memory that possibly could be allocated is for the abbreviation stored in a std::string and all known implementations will fit all known abbreviations into their short string buffer.

  2. If you curtailed history during installation, a runtime_error will be thrown if tp refers to a time_point outside of the range min_year/jan/1 00:00:00 to max_year/dec/31 23:59:59. This can not happen with the default settings of min_year and max_year.

Example:

auto local = current_zone()->to_local(system_clock::now());
cout << local.first << ' ' << local.second << '\n';

Which just output for me:

2015-07-12 16:57:14.430467 EDT

Not quite 5pm in the US Eastern timezone during daylight saving time.

And for a historical example:

auto distant_past = locate_zone("America/New_York")->to_local(day_point(feb/9/1942) + 7h);
cout << distant_past.first << ' ' << distant_past.second << '\n';

Which outputs:

1942-02-09 03:00:00 EWT

The US shifted to "War Time."


If you want to go the other direction (from local time to UTC) use:

template <class Rep, class Period>
std::chrono::time_point<std::chrono::system_clock,
    typename std::common_type<std::chrono::duration<Rep, Period>,
                              std::chrono::seconds>::type>
to_sys(std::chrono::time_point<std::chrono::system_clock,
                               std::chrono::duration<Rep, Period>> tp) const;

For example:

auto distant_past = locate_zone("America/New_York")->to_sys(day_point(feb/9/1942) + 3h);
cout << distant_past << ' ' << " UTC\n";

Which outputs:

1942-02-09 07:00:00 UTC

This function will throw an exception of type nonexistent_local_time if the local time does not exist. This can happen when the local clock is discontinuously set forward, such as when moving from standard time to daylight savings time.

For example:

try
{
    auto distant_past = locate_zone("America/New_York")->to_sys(day_point(feb/9/1942) + 3h - 1ms);
    cout << distant_past << ' ' << " UTC\n";
}
catch (const exception& e)
{
    cout << e.what() << '\n';
}

Which outputs:

1942-02-09 02:59:59.999 is in a gap between
1942-02-09 02:00:00 EST and
1942-02-09 03:00:00 EWT which are both equivalent to
1942-02-09 07:00:00 UTC

And sometimes a local time can be ambiguous, mapping to more than one UTC time:

try
{
    auto distant_past = locate_zone("America/New_York")->to_sys(day_point(sep/30/1945) + 2h - 1ns);
    cout << distant_past << " UTC\n";
}
catch (const exception& e)
{
    cout << e.what() << '\n';
}
1945-09-30 01:59:59.999999999 is ambiguous.  It could be
1945-09-30 01:59:59.999999999 EPT == 1945-09-30 05:59:59.999999999 UTC or
1945-09-30 01:59:59.999999999 EST == 1945-09-30 06:59:59.999999999 UTC

If you would rather not deal with these rare exceptions, you can choose ahead of time to select the earliest time or latest time when a local time falls into a gap:

auto z = locate_zone("America/New_York");
auto distant_past = z->to_sys(day_point(sep/30/1945) + 2h - 1ns, choose::earliest);
cout << distant_past << " UTC\n";
distant_past =      z->to_sys(day_point(sep/30/1945) + 2h - 1ns, choose::latest);
cout << distant_past << " UTC\n";

Which outputs:

1945-09-30 05:59:59.999999999 UTC
1945-09-30 06:59:59.999999999 UTC

When using this form of to_sys and the local time is non-existent, both choices will map to the single UTC time on either side of the gap:

auto z = locate_zone("America/New_York");
auto distant_past = z->to_sys(day_point(feb/9/1942) + 3h - 1ms, choose::earliest);
cout << distant_past << " UTC\n";
distant_past =      z->to_sys(day_point(feb/9/1942) + 3h - 1ms, choose::latest);
cout << distant_past << " UTC\n";

Which outputs:

1942-02-09 07:00:00.000 UTC
1942-02-09 07:00:00.000 UTC

So far I've shown how given a Zone and a system_clock::time_point of arbitrary precision, you can use to_local to map UTC to local time, and to_sys to map local time to UTC, with your choice of either detecting any errors, or choosing how to resolve errors. But what if that is not enough? You may be thinking: Do I have to call these mapping functions every second? How often does the offset change?

This library offers a partial solution to this dilemma. If the location you are concerned about doesn't change, and if the database isn't reloaded, then get_info can tell you how far into the past, and far into the future a given offset and abbreviation are guaranteed to stay valid:

template <class Rep, class Period>
sys_info
get_info(std::chrono::time_point<std::chrono::system_clock,
                                 std::chrono::duration<Rep, Period>> tp,
         tz timezone) const;

Input a time_point tp, and indicate whether tp represents a UTC time_point (tz::utc) or a local time_point (tz::local), and a struct sys_info for that time_point is returned:

auto sys_info = locate_zone("America/New_York")->get_info(system_clock::now(), tz::utc);

Upon return sys_info will contain the following information:

The sys_info also has a streaming operator which is mainly useful for debugging purposes. Here is sample code and output:

cout << current_zone()->get_info(system_clock::now(), tz::utc);

2015-03-08 07:00:00
2015-11-01 06:00:00
-04:00:00
01:00
EDT

This is considered to be a low-level function, and as such there is no error detection if you input a local time that either does not exist, or is ambiguous. Enough information is returned for you to compute those conditions. Indeed, this is exactly how error detection is computed in to_sys: by calling get_info and analyzing how the input time relates to begin and end.

Additionally the Zone is equality and less-than comparable (using the name()). And you can stream the Zone out to a stream, though the output may not be crystal clear. The streaming output is mainly used as an aid in debugging this library, not your code.

Flight Example

There's nothing like a real-world example to help demonstrate things. Imagine a plane flying from New York, New York, USA to Tehran, Iran. To make it more realistic, lets say this flight occurred before the hostage crisis, right at the end of 1978. Flight time for a non-stop one way trip is 14 hours and 44 minutes.

Given that the departure is one minute past noon on Dec. 30, 1978, local time, what is the local arrival time?

#include "tz.h"
#include <iostream>

int
main()
{
    using namespace std::chrono;
    using namespace date;
    auto nyc_tz = locate_zone("America/New_York");
    auto teh_tz = locate_zone("Asia/Tehran");
    auto nyc_departure_sys = nyc_tz->to_sys(day_point(dec/30/1978) + 12h + 1min);
    auto nyc_departure = nyc_tz->to_local(nyc_departure_sys);
    auto flight_length = 14h + 44min;
    auto teh_arrival_sys = nyc_departure_sys + flight_length;
    auto teh_arrival = teh_tz->to_local(teh_arrival_sys);
    std::cout << "departure NYC time:  " << nyc_departure.first << ' '
                                         << nyc_departure.second << '\n';
    std::cout << "flight time is " << make_time(flight_length) << '\n';
    std::cout << "arrival Tehran time: " << teh_arrival.first << ' '
                                         << teh_arrival.second << '\n';
}

There are several points to be made about the above code:

The output of the above program is:

departure NYC time:  1978-12-30 12:01:00 EST
flight time is 14:44
arrival Tehran time: 1978-12-31 11:45:00 IRST

And this program is exactly correct. But what happens with the same flight on the following day?

auto nyc_departure_sys = nyc_tz->to_sys(day_point(dec/31/1978) + 12h + 1min);

departure NYC time:  1978-12-31 12:01:00 EST
flight time is 14:44
arrival Tehran time: 1979-01-01 11:15:00 IRST

Now we have the flight arriving 30min earlier. This is because the time zone "Asia/Tehran" undergoes an offset change while the plane is in the air, shifting its UTC offset to 30min earlier. Is this the final word on this example? Almost. If accuracy down to the second is required (it is not for a flight arrival), then additional effort needs to be expended. See Flight Example with leap seconds.

utc_clock

One of the first questions everyone asks when a new date-time library comes out is:

Does it handle leap seconds?

The answer here is yes, this library can handle leap seconds. But be careful what you ask for. Correctly handling leap seconds is error prone. Therefore this library handles leap seconds in a completely different type-safe way, which can't be accidentally mixed with everything else presented so far. The motivation for this separation is born from several issues:

utc_clock is a std::chrono-conforming clock with the same duration as your system_clock, and a now() function that returns the actual number of physical seconds since 1970-01-01 00:00:00 UTC (counting leap seconds):

class utc_clock
{
public:
    using duration                  = std::chrono::system_clock::duration;
    using rep                       = duration::rep;
    using period                    = duration::period;
    using time_point                = std::chrono::time_point<utc_clock>;
    static constexpr bool is_steady = true;

    static time_point now() noexcept;

    template <class Duration>
        static
        std::chrono::time_point<utc_clock,
            typename std::common_type<Duration, std::chrono::seconds>::type>
        sys_to_utc(std::chrono::time_point<std::chrono::system_clock, Duration> t);

    template <class Duration>
        static
        std::chrono::time_point<std::chrono::system_clock,
            typename std::common_type<Duration, std::chrono::seconds>::type>
        utc_to_sys(std::chrono::time_point<utc_clock, Duration> t);
};

Additionally utc_clock has static member functions for converting between utc_clock-based time_points to and from system_clock-based time_points of any precision. But it is important to remember that utc_clock isn't connected to a super accurate atomic clock. All it does is look its time_point up in the database to see how many leap seconds have passed since 1972, and adds or subtracts that number of seconds to do the conversion. The utc_clock::now() function simply calls system_clock::now() and adds the current total of leaps seconds (currently 26) to the result. This is useful behavior but it is important to understand that utc_clock is not a highly accurate scientific instrument. It is precisely as accurate as your existing std::chrono::system_clock.

Flight Example with leap seconds

In the preceding section a flight from New York City to Tehran was offered, demonstrating how local political changes in the rules governing UTC offsets can affect time computations. As it turns out, while that flight departing on dec/31/1978 was in the air, we also underwent a leap second addition. How does that impact the computation, and how can this library be used to account for that (should it actually be important)?

#include "tz.h"
#include <iostream>

int
main()
{
    using namespace std::chrono;
    using namespace date;
    auto nyc_tz = locate_zone("America/New_York");
    auto teh_tz = locate_zone("Asia/Tehran");
    auto nyc_departure_sys = nyc_tz->to_sys(day_point(dec/31/1978) + 12h + 1min);
    auto nyc_departure = nyc_tz->to_local(nyc_departure_sys);
    auto nyc_departure_utc = utc_clock::sys_to_utc(nyc_departure_sys);
    auto flight_length = 14h + 44min;
    auto teh_arrival_utc = nyc_departure_utc + flight_length;
    auto teh_arrival_sys = utc_clock::utc_to_sys(teh_arrival_utc);
    auto teh_arrival = teh_tz->to_local(teh_arrival_sys);
    std::cout << "departure NYC time:  " << nyc_departure.first << ' '
                                         << nyc_departure.second << '\n';
    std::cout << "flight time is " << make_time(flight_length) << '\n';
    std::cout << "arrival Tehran time: " << teh_arrival.first << ' '
                                         << teh_arrival.second << '\n';
}

departure NYC time:  1978-12-31 12:01:00 EST
flight time is 14:44
arrival Tehran time: 1979-01-01 11:14:59 IRST

As can be seen, we now report an arrival time 1s before the arrival time we computed without taking leap seconds into account. The key to working with leap seconds is to make sure that all your time arithmetic takes place using utc_clock-based time_points, instead of system_clock-based time_points. Just convert to system_clock when you're ready to break the date and time up into field-based structures, or are ready to further convert it into a local time_point. In this example, the only time arithmetic is:

auto teh_arrival_utc = nyc_departure_utc + flight_length;

The reset of the code is simply about converting from local, to system_clock to utc_clock and back.

Digression: Doing computations with leap seconds is cool. But perhaps the true power of this library is revealed in the ease with which I created this example. I sat back and said to myself:

I want to find a time and location where a timezone offset changed within 12 hours of a leap second insertion. And then build my flight time example around that event.

Subsequently I wrote the following code to search the entire planet, and the last 45 years, to find these rare chronological events:

const auto& db = get_tzdb();
for (auto const& leap : db.leaps)
{
    for (auto const& zone : db.zones)
    {
        auto info = zone.get_info(leap.date(), tz::utc);
        if (leap.date() - info.begin <= 12h)
        {
            auto prev = zone.get_info(info.begin - 1s, tz::utc);
            if (prev.offset != info.offset)
                std::cout << zone.name() << "  " << info.begin << " : "
                          <<  leap << ' '
                          << make_time(info.offset-prev.offset) << '\n';
        }
        if (info.end - leap.date() <= 12h)
        {
            auto next = zone.get_info(info.end, tz::utc);
            if (next.offset != info.offset)
                std::cout << zone.name() << " " << info.end <<  " : "
                          <<  leap << ' '
                          << make_time(next.offset - info.offset) << '\n';
        }
    }
}

The flight time example wasn't really about Iran, the US, and politics after all. It was about finding this needle in a haystack of time and space, which turned out to be relatively easy and incredibly efficient.

You too can analyze the IANA Time Zone Database in creative and interesting ways no one else has thought of. There is a lot of history here.

Formatting

All of the types in this library, as well as in date.h are streamable when you need quick and simple output. However in addition to this simplistic streaming there is more sophisticated formatting built on top of the C++11 time_put<char> facet. time_put<char> itself is built on C's strftime function. But time_put<char> is sensitive to C++ locales.

The basic way to use formatting is to call the format function like this:

cout << format("%A %F %T", floor<seconds>(system_clock::now())) << '\n';

Which just output for me:

Sunday 2016-04-03 22:02:19

Note the cast to seconds precision in the call. This is how you control the precision of the seconds output (if any). The modifiers %S and %T will output seconds to whatever the precision is of the time_point. For example:

cout << format("%A %F %T", floor<milliseconds>(system_clock::now())) << '\n';

would instead output:

Sunday 2016-04-03 22:02:19.656

Note that there is an implicit time zone being used here: UTC. The %z and %Z modifiers can be used to show this:

cout << format("%A %F %T %z %Z", floor<milliseconds>(system_clock::now())) << '\n';

would instead output:

Sunday 2016-04-03 22:02:19.656 +0000 UTC

A Zone can also be passed in and then the %z and %Z modifiers will reflect that passed-in zone. It is important to remember however that format never shifts the time_point for you. Instead you pass in a Zone that you know to be associated with your time_point. For example:

auto zone = locate_zone("Europe/Berlin");
auto local = zone->to_local(floor<milliseconds>(system_clock::now())).first;
cout << format("%A %F %T %z %Z", local, zone) << '\n';
Monday 2016-04-04 00:02:19.656 +0200 CEST

The only thing format ever does with a Zone is extract the offset and/or the abbreviation for use with the %z and %Z modifiers.

You can also pass in a locale to format:

cout << format(locale("de_DE"), "%A %F %T %z %Z", local, zone) << '\n';
Montag 2016-04-04 00:02:19,656 +0200 CEST

The set of named locales that your OS supports is defined by your OS, not this library.

Instead of a time_point you can also pass in anything that is implicitly convertible to day_point:

cout << format(locale("de_DE"), "%A %B %e, %Y", 2016_y/jul/mon[1]) << '\n';
Montag Juli  4, 2016

In summary, use format by passing in a format string and a time_point, or something implicitly convertible to a day_point. You can optionally pass in a locale as the first parameter, and a Zone as the last parameter. format will never alter the value of your time_point. The precision of the time_point controls the precision of seconds with the %S and %T modifiers. If you pass in a Zone, this will only impact the output of %z and %Z (which default to +0000 and UTC respectively). The output of format is a std::string.

Parsing

Since all parts of all date-types in this library can be constructed with integral types, you can parse any format you wish as integrals, and create dates from any format you wish that way.

However this section introduces a parse function which is built on top of the C++11 time_get facet which can also be used:

template <class Duration>
void
parse(std::istream& is, const std::string& format,
      std::chrono::time_point<std::chrono::system_clock, Duration>& tp);

You can input any istream, and a format string much like that used for format and strftime, and a time_point of any precision, and this function will attempt to extract the time_point from the istream by using the format string. If not successful, the time_point will not be altered.

Example use:

istringstream is("Montag 2016-04-04 00:02:19,656 +0200");
is.imbue(locale("de_DE"));
system_clock::time_point tp;
parse(is, "%A %F %T %z", tp);
cout << tp << '\n';

Which outputs:

2016-04-03 22:02:19.656000

Note that the locale associated with the istream is respected. If the format string contains a %z which matches the input stream, this is used to convert the value to UTC. If there is no %z, then no conversion happens (you can assume whatever timezone you want). Note that fractional seconds are accepted as long as one uses %T or %S, and the precision of the time_point is fine enough to accept fractional seconds.

%Z is not accepted as the mapping from a timezone abbreviation to UTC is in general, ambiguous. If you have a %Z in the format string, this will result in is.fail() returning true after the call to parse.

However, if you absolutely must parse a timestamp with a timezone abbreviation in it, an extra parse overload is provided:

template <class Duration>
void
parse(std::istream& is, const std::string& format,
      std::chrono::time_point<std::chrono::system_clock, Duration>& tp,
      std::string& abbrev);

Now if %Z matches a word in is and if the rest of is correctly parses according to format, then abbrev will be assigned the word which matched %Z. This will not have any impact on the value of tp (no timezone offset applied). However perhaps there is enough a-priori knowledge in your application to make use of the value of abbrev to correctly interpret the meaning of the timestamp and the resulting value of tp.

As an example of how this option can be both useful and dangerous, consider an example where we need to parse the timestamp "Thu Apr 07 11:45:28 AEST 2016", and we want to discover what the corresponding time is in UTC, and what timezone this timestamp represents.

The following program parses this, and then searches the entire timezone database looking for timezones which have "AEST" as an abbreviation at a local time of Apr 07 11:45:28 2016. The program finds the first one, notes its UTC offset, and then searches for more. If it finds more, and the UTC offset is the same, it simply outputs the name of each additional timezone found. If the additional timezones have a different UTC offset, that is noted too by outputting the UTC timestamp associated with the additional timezone.

#include "tz.h"
#include <string>
#include <iostream>
#include <sstream>
#include <cassert>

int
main()
{
    using namespace std::chrono;
    using namespace date;
    auto& db = get_tzdb();
    std::istringstream in("Thu Apr 07 11:45:28 AEST 2016");
    time_point<system_clock, seconds> tp_local;
    std::string abbrev;
    parse(in, "%a %b %d %T %Z %Y", tp_local, abbrev);
    assert(!in.fail());
    auto i = std::find_if(db.zones.begin(), db.zones.end(),
                          [&tp_local, &abbrev](auto const& z)
                          {
                              return z.get_info(tp_local, tz::local).abbrev == abbrev;
                          });
    if (i != db.zones.end())
    {
        auto tp_utc = i->to_sys(tp_local);
        std::cout << tp_utc << " UTC " << i->name() << '\n';
        for (++i; i != db.zones.end(); ++i)
        {
            if (i->get_info(tp_local, tz::local).abbrev != abbrev)
                continue;
            auto tp = i->to_sys(tp_local);
            if (tp != tp_utc)
                std::cout << tp << " UTC ";
            std::cout << i->name() << '\n';
        }
    }
}

This program outputs:

2016-04-07 01:45:28 UTC Australia/Brisbane
Australia/Currie
Australia/Hobart
Australia/Lindeman
Australia/Melbourne
Australia/Sydney

This indicates that "Thu Apr 07 11:45:28 AEST 2016" unambiguously refers to 2016-04-07 01:45:28 UTC (a UTC offset of +1000). However which IANA timezone is referred to is ambiguous. This means that past or future timepoints using any of these timezones may or may not have the same UTC offsets (or abbreviations) among this set of timezones.

And this is a good case. Consider just altering the abbreviation in the above example from AEST to BST. Now the output is:

2016-04-07 10:45:28 UTC Europe/London
2016-04-07 00:45:28 UTC Pacific/Bougainville

Meaning: Not only do we not know what timezone this refers to, it could mean one of two different UTC timepoints!

So in summary, it is dangerous to parse timezone abbreviations. You should avoid it if at all possible. However, if you are forced to, this library has the power to find out every thing that is knowable about that timestamp.

Reference

Everything specified below is in namespace date, and accessed via the header "tz.h".

The database

The following data structure is the time zone database, and the following functions access it.

struct TZ_DB
{
    std::string            version;
    std::vector<time_zone> zones;
    std::vector<Link>      links;
    std::vector<Leap>      leaps;
    std::vector<Rule>      rules;
};

The TZ_DB database is a singleton. And access to it is read-only, except for reload_tzdb() which re-initializes it. Each vector is sorted to enable fast lookup. You don't have to explicitly program binary search lookups on it. That is handled by the API. But you can explicitly iterate over and inspect this database. And knowing that it is sorted may be of benefit to your inspection logic.

All information in the IANA time zone database is represented in the above TZ_DB data structure, except for the comments in the database. Thus it is up to you, the client of this library, to decide what to do with this data. This library makes it especially easy and convenient to extract the data in the way that is most commonly used (e.g. time conversions among time zones). But it represents all of the data, and hides none of it.

const TZ_DB& get_tzdb();

Effects: If this is the first access to the database, will initialize the database. If tz.cpp was compiled with the configuration macro AUTO_DOWNLOAD == 1, initialization will include checking the IANA website for the latest version, and downloading the latest version if your local version is out of date, or doesn't exist at the location referred to by the install configuration variable in tz.cpp. If tz.cpp was compiled with AUTO_DOWNLOAD == 0, you will have to download and decompress the IANA database from the IANA website and place it at the location referred to by the install configuration variable.

AUTO_DOWNLOAD == 1 requires linking tz.cpp to libcurl.

Returns: A const reference to the database.

Thread Safety: It is safe to call this function from multiple threads at one time. There will be no race to initialize the singleton database as long as your compiler implements threadsafe function-local statics as specified by C++11.

Throws: std::runtime_error if for any reason a reference can not be returned to a valid TZ_DB.

const time_zone* locate_zone(const std::string& tz_name);

Effects: Calls get_tzdb() which will initialize the timezone database if this is the first reference to the database.

Returns: If a time_zone is found for which name() == tz_name, returns a pointer to that time_zone. Otherwise if a Link is found where tz_name == link.name(), then a pointer is returned to the time_zone for which zone.name() == link.target() [Note: A Link is an alternative name for a time_zone. — end note]

Throws: Any exception propagated from get_tzdb(). If a const time_zone* can not be found as described in the Returns clause, throws a std::runtime_error. [Note: On non-exceptional return, the return value is always a pointer to a valid time_zone. — end note]

const time_zone* current_zone();
Effects: Calls locate_zone() which will initialize the timezone database if this is the first reference to the database.

Returns: A const time_zone* referring to the time zone which your computer has set as its local time zone.

Throws: Any exception propagated from locate_zone(). [Note: On non-exceptional return, the return value is always a pointer to a valid time_zone. — end note]

const TZ_DB& reload_tzdb();

Effects:

If If tz.cpp was compiled with the configuration macro AUTO_DOWNLOAD == 1, this function first checks the latest version at the IANA website. If the IANA website is unavailable, or if the latest version is already installed, there are no effects. Otherwise, a new version is available. It is downloaded and installed, and then the program re-initializes the TZ_DB singleton from the new disk files.

If tz.cpp was compiled with the configuration macro AUTO_DOWNLOAD == 0, this function re-initializes the TZ_DB singleton from the disk files. You can manually replace the database without ill-effects after your program has called get_tzdb() and before it calls reload_tzdb(), as there is no access to the files on disk between the first call to get_tzdb() and subsequent calls to reload_tzdb().

Returns: A const reference to the database.

Thread Safety: This function is not thread safe. You must provide your own synchronization among threads accessing the time zone database to safely use this function. If this function re-initializes the database (as it always does when AUTO_DOWNLOAD == 0), all outstanding const time_zone* are invalidated (including those held within zoned_time objects). And afterwards, all outstanding sys_info may hold obsolete data.

Throws: std::runtime_error if for any reason a reference can not be returned to a valid TZ_DB.

The following functions are available only if you compile with the configuration macro HAS_REMOTE_API == 1. Use of this API requires linking to libcurl. AUTO_DOWNLOAD == 1 requires HAS_REMOTE_API == 1. You will be notified at compile time if AUTO_DOWNLOAD == 1 and HAS_REMOTE_API == 0. If HAS_REMOTE_API == 1, then AUTO_DOWNLOAD defaults to 1, otherwise AUTO_DOWNLOAD defaults to 0. On Windows, HAS_REMOTE_API defaults to 0. Everywhere else it defaults to 1. This is because libcurl comes preinstalled everywhere but Windows, but it is available for Windows.

[Note: Even with AUTO_DOWNLOAD == 1, there are no thread-safety issues with this library unless one of the following functions are explicitly called by your code:

const TZ_DB& reload_tzdb();
bool remote_download(const std::string& version);
bool remote_install(const std::string& version);

Once your program has initialized the TZ_DB singleton, that singleton can never be changed without explicit use of reload_tzdb(). — end note]

std::string remote_version();

Returns: The latest database version number from the IANA website. If the IANA website can not be reached, or if it can be reached but the latest version number is unexpectedly not available, the empty string is returned.

Note: If non-empty, this can be compared with get_tzdb().version to discover if you have the latest database installed.

bool remote_download(const std::string& version);

Effects: If version == remote_version() this function will download the compressed tar file holding the latest time zone database from the IANA website. The tar file will be placed at the location indicated by the install configuration variable in tz.cpp.

Returns: true if the database was successfully downloaded, else false.

Thread safety: If called by multiple threads, there will be a race on the creation of the tar file at install.

bool remote_install(const std::string& version);

Effects: If version refers to the file successfully downloaded by remote_download() this function will remove the existing time zone database at install, then extract a new database from the tar file and place it at install, and finally will delete the tar file.

This function does not cause your program to re-initialize itself from this new database. In order to do that, you must call reload_tzdb() (or get_tzdb() if the database has yet to be initialized). If tz.cpp was compiled with AUTO_DOWNLOAD == 1, then reload_tzdb() uses this API to check if the database is out of date, and reinitializes it with a freshly downloaded database only if it needs to. Indeed, if AUTO_DOWNLOAD == 1 there is never any need to call remote_download() or remote_install() explicitly. You can just call reload_tzdb() instead. This API is only exposed so that you can take care of this manually if desired (HAS_REMOTE_API == 1 && AUTO_DOWNLOAD == 0).

Returns: true if the database was successfully replaced by the tar file , else false.

Thread safety: If called by multiple threads, there will be a race on the creation of the new database at install.

Everything else in this library concerns read-only access to this database, and intuitive ways to compute with that information, even while being oblivious to the fact that you are accessing a database.

The entire database on disk occupies less than half of the disk space consumed by an average Beatles song. Don't sweat multiple copies of it. It will easily fit in your smart toaster.

choose

For some conversions from local_time to a sys_time, choose::earliest or choose::latest can be used to convert a non-existent or ambiguous local_time into a sys_time, instead of throwing an exception.

enum class choose {earliest, latest};

nonexistent_local_time

nonexistent_local_time is thrown when one attempts to convert a non-existent local_time to a sys_time without specifying choose::earliest or choose::latest.

class nonexistent_local_time
    : public std::runtime_error
{
public:
    // Construction is undocumented
};

[Example:

#include "tz.h"
#include <iostream>

int
main()
{
    using namespace date;
    using namespace std::chrono_literals;
    try
    {
        auto zt = make_zoned("America/New_York", local_days{sun[2]/mar/2016} + 2h + 30min);
    }
    catch (const nonexistent_local_time& e)
    {
        std::cout << e.what() << '\n';
    }
}

Which outputs:

2016-03-13 02:30:00 is in a gap between
2016-03-13 02:00:00 EST and
2016-03-13 03:00:00 EDT which are both equivalent to
2016-03-13 07:00:00 UTC

— end example:]

ambiguous_local_time

ambiguous_local_time is thrown when one attempts to convert an ambiguous local_time to a sys_time without specifying choose::earliest or choose::latest.

class ambiguous_local_time
    : public std::runtime_error
{
public:
    // Construction is undocumented
};

[Example:

#include "tz.h"
#include <iostream>

int
main()
{
    using namespace date;
    using namespace std::chrono_literals;
    try
    {
        auto zt = make_zoned("America/New_York", local_days{sun[1]/nov/2016} + 1h + 30min);
    }
    catch (const ambiguous_local_time& e)
    {
        std::cout << e.what() << '\n';
    }
}

Which outputs:

2016-11-06 01:30:00 is ambiguous.  It could be
2016-11-06 01:30:00 EDT == 2016-11-06 05:30:00 UTC or
2016-11-06 01:30:00 EST == 2016-11-06 06:30:00 UTC

— end example:]

sys_info

This structure can be obtained from the combination of a time_zone and either a sys_time, or local_time. It can also be obtained from a zoned_time which is effectively a pair of a time_zone and sys_time.

This structure represents a lower-level API. Typical conversions from sys_time to local_time will use this structure implicitly, not explicitly.

struct sys_info
{
    sys_seconds          begin;
    sys_seconds          end;
    std::chrono::seconds offset;
    std::chrono::minutes save;
    std::string          abbrev;
};

The begin and end fields indicate that for the associated time_zone and time_point, the offset and abbrev are in effect in the range [begin, end). This information can be used to efficiently iterate the transitions of a time_zone.

The offset field indicates the UTC offset in effect for the associated time_zone and time_point. The relationship between local_time and sys_time is:

offset = local_time - sys_time

The save field is "extra" information not normally needed for conversion between local_time and sys_time. If save != 0min, this sys_info is said to be on "daylight saving" time, and offset - save suggests what this time_zone might use if it were off daylight saving. However this information should not be taken as authoritative. The only sure way to get such information is to query the time_zone with a time_point that returns an sys_info where save == 0min. There is no guarantee what time_point might return such an sys_info except that it is guaranteed not to be in the range [begin, end) (if save != 0min for this sys_info).

The abbrev field indicates the current abbreviation used for the associated time_zone and time_point. Abbreviations are not unique among the time_zones, and so one can not reliably map abbreviations back to a time_zone and UTC offset.

You can stream out a sys_info:

std::ostream& operator<<(std::ostream& os, const sys_info& r);

local_info

This structure represents a lower-level API. Typical conversions from local_time to sys_time will use this structure implicitly, not explicitly.

struct local_info
{
    enum {unique, nonexistent, ambiguous} result;
    sys_info first;
    sys_info second;
};

When a local_time to sys_time conversion is unique, result == unique, first will be filled out with the correct sys_info and second will be zero-initialized. If the conversion stems from a nonexistent local_time then result == nonexistent, first will be filled out with the sys_info that ends just prior to the local_time and second will be filled out with the sys_info that begins just after the local_time. If the conversion stems from an ambiguous local_time then result == ambiguous, first will be filled out with the sys_info that ends just after the local_time and second will be filled out with the sys_info that starts just before the local_time.

You can stream out a local_info:

std::ostream& operator<<(std::ostream& os, const local_info& r);

time_zone

A time_zone represents all time zone transitions for a specific geographic area. time_zone construction is undocumented, and done for you during the database initialization. You can gain const access to a time_zone via functions such as locate_zone.

class time_zone
{
public:
    time_zone(const time_zone&) = delete;
    time_zone& operator=(const time_zone&) = delete;

    const std::string& name() const;

    template <class Duration> sys_info   get_info(sys_time<Duration> st) const;
    template <class Duration> local_info get_info(local_time<Duration> tp) const;

    template <class Duration>
        sys_time<typename std::common_type<Duration, std::chrono::seconds>::type>
        to_sys(local_time<Duration> tp) const;

    template <class Duration>
        sys_time<typename std::common_type<Duration, std::chrono::seconds>::type>
        to_sys(local_time<Duration> tp, choose z) const;

    template <class Duration>
        local_time<typename std::common_type<Duration, std::chrono::seconds>::type>
        to_local(sys_time<Duration> tp) const;
};

bool operator==(const time_zone& x, const time_zone& y);
bool operator!=(const time_zone& x, const time_zone& y);
bool operator< (const time_zone& x, const time_zone& y);
bool operator> (const time_zone& x, const time_zone& y);
bool operator<=(const time_zone& x, const time_zone& y);
bool operator>=(const time_zone& x, const time_zone& y);

std::ostream& operator<<(std::ostream& os, const time_zone& z)
const std::string& time_zone::name() const;

Returns: The name of the time_zone.

Example: "America/New_York".

Note: Here is an unofficial list of time_zone names: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones.

template <class Duration> sys_info time_zone::get_info(sys_time<Duration> st) const;

Returns: A sys_info i for which st is in the range [i.begin, i.end).

template <class Duration> local_info time_zone::get_info(local_time<Duration> tp) const;

Returns: A local_info for tp.

template <class Duration>
sys_time<typename std::common_type<Duration, std::chrono::seconds>::type>
time_zone::to_sys(local_time<Duration> tp) const;

Returns: A sys_time that is at least as fine as seconds, and will be finer if the argument tp has finer precision. This sys_time is the UTC equivalent of tp according to the rules of this time_zone.

Throws: If the conversion from tp to a sys_time is ambiguous, throws ambiguous_local_time. If the conversion from tp to a sys_time is nonexistent, throws nonexistent_local_time.

template <class Duration>
sys_time<typename std::common_type<Duration, std::chrono::seconds>::type>
time_zone::to_sys(local_time<Duration> tp, choose z) const;

Returns: A sys_time that is at least as fine as seconds, and will be finer if the argument tp has finer precision. This sys_time is the UTC equivalent of tp according to the rules of this time_zone. If the conversion from tp to a sys_time is ambiguous, returns the earlier sys_time if z == choose::earliest, and returns the later sys_time if z == choose::latest. If the tp represents a non-existent time between two UTC time_points, then the two UTC time_points will be the same, and that UTC time_point will be returned.

template <class Duration>
local_time<typename std::common_type<Duration, std::chrono::seconds>::type>
time_zone::to_local(sys_time<Duration> tp) const;

Returns: The local_time associated with tp and this time_zone.

bool operator==(const time_zone& x, const time_zone& y);

Returns: x.name() == y.name().

bool operator!=(const time_zone& x, const time_zone& y);

Returns: !(x == y).

bool operator<(const time_zone& x, const time_zone& y);

Returns: x.name() < y.name().

bool operator>(const time_zone& x, const time_zone& y);

Returns: y < x.

bool operator<=(const time_zone& x, const time_zone& y);

Returns: !(y < x).

bool operator>=(const time_zone& x, const time_zone& y);

Returns: !(x < y).

std::ostream& operator<<(std::ostream& os, const time_zone& z)

Produces an output that is probably more meaningful to me than it is to you. I found it useful for debugging this library.

zoned_time

zoned_time represents a logical paring of time_zone and a time_point with precision Duration. If seconds is not implicitly convertible to Duration, the instantiation is ill-formed. [Note: There exist time_zones with UTC offsets that require a precision of seconds. — end note:]

template <class Duration>
class zoned_time
{
    const time_zone*   zone_;  // exposition only
    sys_time<Duration> tp_;    // exposition only

public:
    zoned_time(const zoned_time&) = default;
    zoned_time& operator=(const zoned_time&) = default;

             zoned_time(sys_time<Duration> st);
    explicit zoned_time(const time_zone* z);
    explicit zoned_time(const std::string& name);

    template <class Duration2,
              class = std::enable_if_t
                      <
                          std::is_convertible<sys_time<Duration2>,
                                              sys_time<Duration>>{}
                      >>
        zoned_time(const zoned_time<Duration2>& zt) noexcept;

    zoned_time(const time_zone* z,      local_time<Duration> tp);
    zoned_time(const std::string& name, local_time<Duration> tp);
    zoned_time(const time_zone* z,      local_time<Duration> tp, choose c);
    zoned_time(const std::string& name, local_time<Duration> tp, choose c);

    zoned_time(const time_zone* z,      const zoned_time<Duration>& zt);
    zoned_time(const std::string& name, const zoned_time<Duration>& zt);
    zoned_time(const time_zone* z,      const zoned_time<Duration>& zt, choose);
    zoned_time(const std::string& name, const zoned_time<Duration>& zt, choose);

    zoned_time(const time_zone* z,      const sys_time<Duration>& st);
    zoned_time(const std::string& name, const sys_time<Duration>& st);

    zoned_time& operator=(sys_time<Duration> st);
    zoned_time& operator=(local_time<Duration> ut);

             operator sys_time<Duration>() const;
    explicit operator local_time<Duration>() const;

    const time_zone*     get_time_zone() const;
    local_time<Duration> get_local_time() const;
    sys_time<Duration>   get_sys_time() const;
    sys_info             get_info() const;
};

using zoned_seconds = zoned_time<std::chrono::seconds>;

template <class Duration1, class Duration2>
bool
operator==(const zoned_time<Duration1>& x, const zoned_time<Duration2>& y);

template <class Duration1, class Duration2>
bool
operator!=(const zoned_time<Duration1>& x, const zoned_time<Duration2>& y);

An invariant of zoned_time<Duration> is that it always refers to a valid time_zone, and represents a point in time that exists and is not ambiguous.

zoned_time<Duration>::zoned_time(const zoned_time&) = default;
zoned_time<Duration>& zoned_time<Duration>::operator=(const zoned_time&) = default;

The copy members transfer the associated time_zone from the source to the destination. After copying, source and destination compare equal. If Duration has noexcept copy members, then zoned_time<Duration> has noexcept copy members.

zoned_time<Duration>::zoned_time(sys_time<Duration> st);

Effects: Constructs a zoned_time zt such that zt.get_time_zone()->name() == "UTC", and zt.get_sys_time() == st.

explicit zoned_time<Duration>::zoned_time(const time_zone* z);

Requires: z refers to a valid time_zone.

Effects: Constructs a zoned_time zt such that zt.get_time_zone()-> == z, and zt.get_sys_time() == sys_seconds{}.

explicit zoned_time<Duration>::zoned_time(const std::string& name);

Effects: Equivalent to construction with locate_zone(name).

Throws: Any exception propagating out of locate_zone(name).

template <class Duration2,
          class = std::enable_if_t
                  <
                      std::is_convertible<sys_time<Duration2>,
                                          sys_time<Duration>>{}
                  >>
    zoned_time<Duration>::zoned_time(const zoned_time<Duration2>& y) noexcept;

Effects: Constructs a zoned_time x such that x == y.

zoned_time<Duration>::zoned_time(const time_zone* z, local_time<Duration> tp);

Requires: z refers to a valid time_zone.

Effects: Constructs a zoned_time zt such that zt.get_time_zone()-> == z, and zt.get_local_time() == tp.

Throws: Any exception that z->to_sys(tp) would throw.

zoned_time<Duration>::zoned_time(const std::string& name, local_time<Duration> tp);

Effects: Equivalent to construction with {locate_zone(name), tp}.

zoned_time<Duration>::zoned_time(const time_zone* z, local_time<Duration> tp, choose c);

Requires: z refers to a valid time_zone.

Effects: Constructs a zoned_time zt such that zt.get_time_zone()-> == z, and zt.get_sys_time() == z->to_sys(tp, c).

zoned_time<Duration>::zoned_time(const std::string& name, local_time<Duration> tp, choose c);

Effects: Equivalent to construction with {locate_zone(name), tp, c}.

zoned_time<Duration>::zoned_time(const time_zone* z, const zoned_time<Duration>& y);

Requires: z refers to a valid time_zone.

Effects: Constructs a zoned_time zt such that zt.get_time_zone()-> == z, and zt.get_sys_time() == y.get_sys_time().

zoned_time<Duration>::zoned_time(const std::string& name, const zoned_time<Duration>& y);

Effects: Equivalent to construction with {locate_zone(name), y}.

zoned_time<Duration>::zoned_time(const time_zone* z, const zoned_time<Duration>& y, choose);

Requires: z refers to a valid time_zone.

Effects: Constructs a zoned_time zt such that zt.get_time_zone()-> == z, and zt.get_sys_time() == y.get_sys_time().

Note: The choose parameter is allowed here, but has no impact.

zoned_time<Duration>::zoned_time(const std::string& name, const zoned_time<Duration>& y, choose);

Effects: Equivalent to construction with {locate_zone(name), y}.

Note: The choose parameter is allowed here, but has no impact.

zoned_time<Duration>::zoned_time(const time_zone* z, const sys_time<Duration>& st);

Requires: z refers to a valid time_zone.

Effects: Constructs a zoned_time zt such that zt.get_time_zone()-> == z, and zt.get_sys_time() == st.

zoned_time<Duration>::zoned_time(const std::string& name, const sys_time<Duration>& st);

Effects: Equivalent to construction with {locate_zone(name), st}.

zoned_time<Duration>& zoned_time<Duration>::operator=(sys_time<Duration> st);

Effects: After assignment get_sys_time() == st. This assignment has no effect on the return value of get_time_zone().

Returns: *this.

zoned_time<Duration>& zoned_time<Duration>::operator=(local_time<Duration> lt);

Effects: After assignment get_local_time() == lt. This assignment has no effect on the return value of get_time_zone().

Returns: *this.

zoned_time<Duration>::operator sys_time<Duration>() const;

Returns: get_sys_time().

explicit zoned_time<Duration>::operator local_time<Duration>() const;

Returns: get_local_time().

const time_zone* zoned_time<Duration>::get_time_zone() const;

Returns: zone_.

local_time<Duration> zoned_time<Duration>::get_local_time() const;

Returns: zone_->to_local(tp_).

sys_time<Duration> zoned_time<Duration>::get_sys_time() const;

Returns: tp_.

sys_info zoned_time<Duration>::get_info() const;

Returns: zone_->get_info(tp_).

template <class Duration1, class Duration2>
bool
operator==(const zoned_time<Duration1>& x, const zoned_time<Duration2>& y);

Returns: x.zone_ == y.zone_ && x.tp_ == y.tp_.

template <class Duration1, class Duration2>
bool
operator!=(const zoned_time<Duration1>& x, const zoned_time<Duration2>& y);

Returns: !(x == y).

template 
std::ostream&
operator<<(std::ostream& os, const zoned_time& t)

Effects: Streams t to os using the format "%F %T %Z" and the value returned from t.get_local_time().

Returns: os.

make_zoned

There exist several overloaded functions named make_zoned which serve as factory functions for zoned_time<Duration> and will deduce the correct Duration from the argument list. In every case the correct return type is zoned_time<std::common_type_t<Duration, std::chrono::seconds>>.

template <class Duration>
zoned_time<std::common_type_t<Duration, std::chrono::seconds>>
make_zoned(sys_time<Duration> tp)

Returns: {tp}.

template <class Duration>
zoned_time<std::common_type_t<Duration, std::chrono::seconds>>
make_zoned(const time_zone* zone, local_time<Duration> tp)

Returns: {zone, tp}.

template <class Duration>
zoned_time<std::common_type_t<Duration, std::chrono::seconds>>
make_zoned(const std::string& name, local_time<Duration> tp)

Returns: {name, tp}.

template <class Duration>
zoned_time<std::common_type_t<Duration, std::chrono::seconds>>
make_zoned(const time_zone* zone, local_time<Duration> tp, choose c)

Returns: {zone, tp, c}.

template <class Duration>
zoned_time<std::common_type_t<Duration, std::chrono::seconds>>
make_zoned(const std::string& name, local_time<Duration> tp, choose c)

Returns: {name, tp, c}.

template <class Duration>
zoned_time<std::common_type_t<Duration, std::chrono::seconds>>
make_zoned(const time_zone* zone, const zoned_time<Duration>& zt)

Returns: {zone, zt}.

template <class Duration>
zoned_time<std::common_type_t<Duration, std::chrono::seconds>>
make_zoned(const std::string& name, const zoned_time<Duration>& zt)

Returns: {name, zt}.

template <class Duration>
zoned_time<std::common_type_t<Duration, std::chrono::seconds>>
make_zoned(const time_zone* zone, const zoned_time<Duration>& zt, choose c)

Returns: {zone, zt, c}.

template <class Duration>
zoned_time<std::common_type_t<Duration, std::chrono::seconds>>
make_zoned(const std::string& name, const zoned_time<Duration>& zt, choose c)

Returns: {name, zt, c}.

template <class Duration>
zoned_time<std::common_type_t<Duration, std::chrono::seconds>>
make_zoned(const time_zone* zone, const sys_time<Duration>& st)

Returns: {zone, st}.

template <class Duration>
zoned_time<std::common_type_t<Duration, std::chrono::seconds>>
make_zoned(const std::string& name, const sys_time<Duration>& st)

Returns: {name, st}.

format

template <class Duration>
std::string
format(const std::locale& loc, std::string format, local_time<Duration> tp);

template <class Duration>
std::string
format(std::string format, local_time<Duration> tp);

template <class Duration>
std::string
format(const std::locale& loc, std::string format, const zoned_time<Duration>& tp);

template <class Duration>
std::string
format(std::string format, const zoned_time<Duration>& tp);

template <class Duration>
std::string
format(const std::locale& loc, std::string format, sys_time<Duration> tp);

template <class Duration>
std::string
format(std::string format, sys_time<Duration> tp);

Effects: These functions create a formatted time stamp using the arguments, returning the result in a std::string.

If a locale is passed in, then that locale is used for any formatting that requires a locale. If no locale is passed in, then if a locale is required for formatting, a default constructed locale will be used (which makes a copy of the global locale).

The format string follows the rules as specified for std::time_put with the following exceptions:

  • If %S or %T appears in the format string and the argument tp has precision finer than seconds, then seconds are formatted as a decimal floating point number with a fixed format and a precision matching that of the precision of tp. The character for the decimal point is localized according to the locale.

  • If %z appears in the format, the behavior depends on the type of tp:

    • local_time: An exception of type std::runtime_error is thrown.
    • zoned_time: The offset associated with tp.get_time_zone() is used.
    • sys_time: "+0000" is used.
  • If %Z appears in the format, the behavior depends on the type of tp:

    • local_time: An exception of type std::runtime_error is thrown.
    • zoned_time: The abbreviation associated with tp.get_time_zone() is used.
    • sys_time: "UTC" is used.

For the overloads taking a zoned_time it is the value returned by tz.get_local_time() that is formatted.

Returns: The formatted string.

parse

template <class Duration>
void
parse(std::istream& is, const std::string& format, sys_time<Duration>& tp);

template <class Duration>
void
parse(std::istream& is, const std::string& format, local_time<Duration>& tp);

template <class Duration>
void
parse(std::istream& is, const std::string& format, local_time<Duration>& tp,
      std::string& abbrev);

Effects: These functions attempt to parse a time_point out of is according to format. If the parse is unsuccessful, calls is.setstate(std::ios::failbit) which may throw an exception. tp is altered only in the event of a successful parse.

The format string follows the rules as specified for std::time_get with the following exceptions:

  • If %S or %T appears in the format string and the argument tp has precision finer than seconds, then the seconds are parsed as a double, and if that parse is successful contributes to the time stamp as if round<Duration>(duration<double>{s}) where s is a local variable holding the parsed double.

  • If %z appears in the format string and an offset is successfully parsed, the first overload (sys_time) interprets the parsed time as a local time and subtracts the offset prior to assigning the value to tp, resulting in a value of tp representing a UTC timestamp. The second and third overloads require a valid parse of the offset, but then ignore the offset in assigning a value to the local_time<Duration>& tp.

  • If %Z appears in the format string then an abbreviation is required in that position for a successful parse. However the parsed abbreviation does not have to be a valid time zone abbreviation, and has no impact on the value parsed into tp. Using the third overload one can discover what that parsed abbreviation is. If the third overload is used, but %Z does not appear in the format, then abbrev is not altered.

Note: There is no unique mapping from a time zone abbreviation to a time_zone.

utc_clock

class utc_clock
{
public:
    using duration                  = std::chrono::system_clock::duration;
    using rep                       = duration::rep;
    using period                    = duration::period;
    using time_point                = std::chrono::time_point<utc_clock>;
    static constexpr bool is_steady = true;

    static time_point now() noexcept;

    template <class Duration>
        static
        utc_time<std::common_type_t<Duration, std::chrono::seconds>>
        sys_to_utc(sys_time<Duration> t);

    template <class Duration>
        static
        sys_time<std::common_type_t<Duration, std::chrono::seconds>>
        utc_to_sys(utc_time<Duration> u);
};

template <class Duration>
    using utc_time = std::chrono::time_point<utc_clock, Duration>;

using utc_seconds = utc_time<std::chrono::seconds>;

In contrast to sys_time which does not take leap seconds into account, utc_clock and its associated time_point, utc_time, counts time, including leap seconds, since 1970-01-01 00:00:00 UTC. It also provides functions for converting between utc_time and sys_time. These functions consult get_tzdb().leaps to decide how many seconds to add/subtract in performing those conversions.

static utc_clock::time_point utc_clock::now() noexcept;

Returns: sys_to_utc(system_clock::now()).

template <class Duration>
static
utc_time<std::common_type_t<Duration, std::chrono::seconds>>
utc_clock::sys_to_utc(sys_time<Duration> t);

Returns: A utc_time u, such that u.time_since_epoch() - t.time_since_epoch() is equal to the number of leap seconds that were inserted between t and 1970-01-01. If t is ambiguous on this issue (i.e. corresponds to the date of leap second insertion), then the conversion counts that leap second as inserted.

template <class Duration>
static
sys_time<std::common_type_t<Duration, std::chrono::seconds>>
utc_clock::utc_to_sys(utc_time<Duration> u);

Returns: A sys_time t, such that utc_clock::sys_to_utc(t) == u.

template <class Duration>
utc_time<std::common_type_t<Duration, std::chrono::seconds>>
to_utc_time(sys_time<Duration> t)

Returns: utc_clock::sys_to_utc(t).

template <class Duration>
sys_time<std::common_type_t<Duration, std::chrono::seconds>>
to_sys_time(utc_time<Duration> u)

Returns: utc_clock::utc_to_sys(u).

[Example:

#include "tz.h"
#include <iostream>

int
main()
{
    using namespace date;
    using namespace std::chrono_literals;
    auto t0 = sys_days{1972_y/jul/1} - 1ms;
    auto u0 = to_utc_time(t0);
    auto t1 = to_sys_time(u0);
    std::cout << t0 << ":\n";
    std::cout << (u0.time_since_epoch() - t0.time_since_epoch()).count() << "ms\n";
    std::cout << (t1 - t0).count() << "ms\n\n";

    t0 += 1ms;
    u0 = to_utc_time(t0);
    t1 = to_sys_time(u0);
    std::cout << t0 << ":\n";
    std::cout << (u0.time_since_epoch() - t0.time_since_epoch()).count() << "ms\n";
    std::cout << (t1 - t0).count() << "ms\n";
}

Output:

1972-06-30 23:59:59.999:
0ms
0ms

1972-07-01 00:00:00.000:
1000ms
0ms

— end example]

Leap

class Leap
{
public:
    Leap(const Leap&)            = default;
    Leap& operator=(const Leap&) = default;
    
    // Undocumented constructors

    sys_seconds date() const;
};

bool operator==(const Leap& x, const Leap& y);
bool operator!=(const Leap& x, const Leap& y);
bool operator< (const Leap& x, const Leap& y);
bool operator> (const Leap& x, const Leap& y);
bool operator<=(const Leap& x, const Leap& y);
bool operator>=(const Leap& x, const Leap& y);

template <class Duration> bool operator==(const const Leap&         x, const sys_time<Duration>& y);
template <class Duration> bool operator==(const sys_time<Duration>& x, const Leap&               y);
template <class Duration> bool operator!=(const Leap&               x, const sys_time<Duration>& y);
template <class Duration> bool operator!=(const sys_time<Duration>& x, const Leap&               y);
template <class Duration> bool operator< (const Leap&               x, const sys_time<Duration>& y);
template <class Duration> bool operator< (const sys_time<Duration>& x, const Leap&               y);
template <class Duration> bool operator> (const Leap&               x, const sys_time<Duration>& y);
template <class Duration> bool operator> (const sys_time<Duration>& x, const Leap&               y);
template <class Duration> bool operator<=(const Leap&               x, const sys_time<Duration>& y);
template <class Duration> bool operator<=(const sys_time<Duration>& x, const Leap&               y);
template <class Duration> bool operator>=(const Leap&               x, const sys_time<Duration>& y);
template <class Duration> bool operator>=(const sys_time<Duration>& x, const Leap&               y);

Leap is a copyable class that is constructed and stored in the time zone database when initialized. You can explicitly convert it to a sys_seconds with the member function date() and that will be the date of the leap second insertion. Leap is equality and less-than comparable, both with itself, and with sys_time<Duration>.

Link

class Link
{
public:
    Link(const Link&)            = default;
    Link& operator=(const Link&) = default;

    // Undocumented constructors

    const std::string& name()   const;
    const std::string& target() const;
};

bool operator==(const Link& x, const Link& y);
bool operator!=(const Link& x, const Link& y);
bool operator< (const Link& x, const Link& y);
bool operator> (const Link& x, const Link& y);
bool operator<=(const Link& x, const Link& y);
bool operator>=(const Link& x, const Link& y);

A Link is an alternative name for a time_zone. The alternative name is name(). The name of the time_zone for which this is an alternative name is target(). Links will be constructed for you when the time zone database is initialized.

Installation

You will need the following four source files: date.h, tz.h, tz_private.h and tz.cpp. These sources are located at the github repository https://github.com/HowardHinnant/date. The source tz.cpp contains the following string near the top:

static std::string install{"~/Downloads/tzdata"};  //  "c:\\tzdata" on Windows

You should set this such that install points to the directory where your library or application can find the downloaded and uncompressed IANA Time Zone Database (or where you want the software to install it for you if you compile with AUTO_DOWNLOAD == 1).

There are three configuration macros that can be defined on the command line during compilation, or you can ignore them and they will take on default values.

HAS_REMOTE_API Defaults to 1 on Linux and OS X, and to 0 on Windows
AUTO_DOWNLOAD Defaults to HAS_REMOTE_API
LAZY_INIT Defaults to 1

If HAS_REMOTE_API is 1 then the remote API exists, else it doesn't:

std::string remote_version();
bool        remote_download(const std::string& version);
bool        remote_install(const std::string& version);

The remote API requires linking against libcurl (https://curl.haxx.se/libcurl). On OS X and Linux this is done with -lcurl. libcurl comes pre-installed on OS X and Linux, but not on Windows. However one can download it for Windows.

If AUTO_DOWNLOAD is 1 then first access to the timezone database will install it if it hasn't been installed, and if it has, will use the remote API to install the latest version if not already installed.

Optional installation tweaks

If LAZY_INIT is on, the Zones are not fully compiled upon first access to the database. As each Zone is accessed individaully by the programmer (when they are used), they are fully compiled at that point. However, this further Zone compilation does not involve any access to the local copy of the tz database files.

If LAZY_INIT is off, every Zone is fully compiled upon first access to the database.

LAZY_INIT speeds up the initialization of the database, but slows down the first use of any individual Zone. If you are only using a few Zones then LAZY_INIT is a clear win. If you are immediately using all of the Zones (say for some database analysis) then LAZY_INIT is not a win.

If LAZY_INIT is off, and you are on multi-core hardware, and your application has other unrelated initialization it has to take care of, spinning off timezone initialization into a detached thread can be an attractive option:

int
main()
{
    std::thread(date::get_tzdb).detach();
    // other initialization ...
}

By the time your application actually needs to use the timezone database, it is likely to be fully initialized and ready to go. And if it is not, C++11 threadsafe function local statics ensure there is no race condition on the initialization.

If you would like to trade off functionality for size, you can reduce the size of the database in two ways:

You can limit geography by removing one or more of the files in this list:

const std::vector<const std::string> files =
{
    "africa", "antarctica", "asia", "australasia", "backward", "etcetera", "europe",
    "pacificnew", "northamerica", "southamerica", "systemv", "leapseconds"
};

You can limit history by setting min_year to something more recent such as:

CONSTDATA auto min_year = 2015_y;

When you do so, if you ask to convert a date prior to min_year, an exception will be thrown.

The entire database consumes about 859Kb.

Compile tz.cpp in with the rest of your library or application.

If AUTO_DOWNLOAD is not enabled, you are responsible for keeping your IANA Time Zone Database up to date. New versions of it are released several times a year. This library is not bundled with a specific version of the database already installed, nor is any specific version of the database blessed.

There is no preprocessing of the IANA Time Zone Database required. This library efficiently initializes itself directly from the files of the IANA Time Zone Database.

Acknowledgements

A database parser is nothing without its database. I would like to thank the founding contributor of the IANA Time Zone Database Arthur David Olson. I would also like to thank the entire group of people who continually maintain it, and especially the IESG-designated TZ Coordinator, Paul Eggert. Without the work of these people, this software would have no data to parse.

I would also like to thank Jiangang Zhuang and Bjarne Stroustrup for invaluable feedback for the timezone portion of this library, which ended up also influencing the date.h library.

And I would also especially like to thank contributors to this library: gmcode, Ivan Pizhenko, tomy2105 and Ville Voutilainen.