forked from HowardHinnant/date
2107 lines
73 KiB
HTML
2107 lines
73 KiB
HTML
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
|
"http://www.w3.org/TR/html4/loose.dtd">
|
|
<html>
|
|
<head>
|
|
<title>Time Zone Database Parser</title>
|
|
|
|
<style>
|
|
p {text-align:justify}
|
|
li {text-align:justify}
|
|
blockquote.note
|
|
{
|
|
background-color:#E0E0E0;
|
|
padding-left: 15px;
|
|
padding-right: 15px;
|
|
padding-top: 1px;
|
|
padding-bottom: 1px;
|
|
}
|
|
ins {color:#00A000}
|
|
del {color:#A00000}
|
|
code {white-space:pre;}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<address align=right>
|
|
<br/>
|
|
<br/>
|
|
<a href="mailto:howard.hinnant@gmail.com">Howard E. Hinnant</a><br/>
|
|
2016-04-09<br/>
|
|
<a rel="license" href="http://creativecommons.org/licenses/by/4.0/"> <img alt="Creative
|
|
Commons License" style="border-width:0"
|
|
src="http://i.creativecommons.org/l/by/4.0/80x15.png" /></a><br /> This work is licensed
|
|
under a <a rel="license" href="http://creativecommons.org/licenses/by/4.0/">Creative
|
|
Commons Attribution 4.0 International License</a>.
|
|
</address>
|
|
<hr/>
|
|
<h1 align=center>Time Zone Database Parser</h1>
|
|
|
|
<h2>Contents</h2>
|
|
|
|
<ul>
|
|
<li><a href="#Introduction">Introduction</a></li>
|
|
<li><a href="#Overview">Overview</a></li>
|
|
<li><a href="#Synopsis">Synopsis</a></li>
|
|
<li><a href="#Description">Description</a>
|
|
<ul>
|
|
<li><a href="#TheDatabase">The Database</a></li>
|
|
<li><a href="#remote">The remote API</a></li>
|
|
<li><a href="#Zone">Zone</a>
|
|
<ul>
|
|
<li><a href="#Infrastructure">Infrastructure</a></li>
|
|
<li><a href="#ZoneContinued">Zone continued</a></li>
|
|
</ul>
|
|
</li>
|
|
<li><a href="#flightexample1">Flight Example</a></li>
|
|
<li><a href="#utc_clock">utc_clock</a></li>
|
|
<li><a href="#flightexample2">Flight Example with leap seconds</a></li>
|
|
<li><a href="#Formatting">Formatting</a></li>
|
|
<li><a href="#Parsing">Parsing</a></li>
|
|
<li><a href="#ThreadSafety">Thread Safety</a></li>
|
|
</ul>
|
|
</li>
|
|
<li><a href="#Installation">Installation — this is where you find the implementation</a></li>
|
|
<li><a href="#Acknowledgements">Acknowledgements</a></li>
|
|
</ul>
|
|
|
|
<a name="Introduction"></a><h2>Introduction</h2>
|
|
|
|
<p>
|
|
I had just completed writing <a href="date_v2.html"><code>date</code></a>, which is a
|
|
library for extending <code><chrono></code> 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
|
|
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.
|
|
</p>
|
|
|
|
<p>
|
|
This library adds timezone and leap second support to this <a href="date_v2.html">date</a>
|
|
library. This is a separate library from <a href="date_v2.html"><code>date</code></a>
|
|
because many clients of <a href="date_v2.html"><code>date</code></a> do not need timezone
|
|
nor leap second support, and this support does not come for free (though the cost is quite
|
|
reasonable).
|
|
</p>
|
|
|
|
<p>
|
|
This library is a <b>complete</b> parser of the <a
|
|
href="http://www.iana.org/time-zones">IANA Time Zone Database</a>. This database contains
|
|
timezone information that represents the history of local time for many representative
|
|
locations around the globe. It is updated periodically 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.
|
|
</p>
|
|
|
|
<p>
|
|
The <a href="http://www.iana.org/time-zones">IANA Time Zone Database</a> contains four
|
|
specific types of data:
|
|
</p>
|
|
|
|
<ol>
|
|
<li><p>
|
|
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).
|
|
</p></li>
|
|
<li><p>
|
|
Rule: A specification for a single daylight-saving rule. This helps implement and
|
|
consolidate the specifications of Zones.
|
|
</p></li>
|
|
<li><p>
|
|
Link: This is an alternative name for a Zone.
|
|
</p></li>
|
|
<li><p>
|
|
Leap: The date of the insertion of a leap second.
|
|
</p></li>
|
|
</ol>
|
|
|
|
<p>
|
|
The library documented herein provides access to <i>all</i> of this data, and offers
|
|
efficient and convenient ways to compute with it. And this is all done based on the <a
|
|
href="date_v2.html"><code>date</code></a> library, which in turn is based on the C++11/14
|
|
<code><chrono></code> library. So once you've learned those fundamental libraries,
|
|
the learning curve for this library is greatly eased.
|
|
</p>
|
|
|
|
<a name="Overview"></a><h2>Overview</h2>
|
|
|
|
<p>
|
|
Like <a href="date_v2.html"><code>date</code></a>, this library revolves around
|
|
<code>std::chrono::system_clock</code>. Thus it is important to understand the properties
|
|
of this foundation. <code>std::chrono::system_clock</code> has nested types including
|
|
<code>time_point</code> and <code>duration</code>, and a static function called
|
|
<code>now</code> which returns a <code>time_point</code>. This <code>time_point</code>
|
|
measures time against some unspecified epoch using the units of <code>duration</code>.
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
namespace std { namespace chrono {
|
|
|
|
class system_clock
|
|
{
|
|
public:
|
|
using duration = microseconds;
|
|
using time_point = chrono::time_point<system_clock>;
|
|
// other types ...
|
|
|
|
static time_point now() noexcept;
|
|
// other member functions ...
|
|
};
|
|
|
|
}} // namespace std::chrono
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
The use of <code>microseconds</code> above for the <code>duration</code> type is just
|
|
an example. This is what is used for <a href="http://libcxx.llvm.org">libc++</a> on
|
|
<a href="http://www.apple.com/osx/">OS X</a>. Other implementations may use
|
|
other units such as <code>nanoseconds</code>. But one unspecified property that all
|
|
implementations of <code>std::chrono::system_clock</code> have in common is that they
|
|
all model <a href="https://en.wikipedia.org/wiki/Unix_time">Unix time</a>.
|
|
</p>
|
|
|
|
<p>
|
|
<a href="https://en.wikipedia.org/wiki/Unix_time">Unix time</a> counts the number of
|
|
seconds since 1970-01-01 00:00:00 UTC, except that leap seconds are ignored in the count.
|
|
An interesting and useful property of
|
|
<a href="https://en.wikipedia.org/wiki/Unix_time">Unix time</a> is that it treats leap
|
|
seconds as nothing more than a typical clock correction. That is, no clock keeps perfect
|
|
time, not even the one in your computer. And several times a day your computer will ask
|
|
another computer what time it is, and typically correct itself by a small fraction of a
|
|
second.
|
|
</p>
|
|
|
|
<p>
|
|
When a leap second occurs, instead of counting an extra second,
|
|
<a href="https://en.wikipedia.org/wiki/Unix_time">Unix time</a> reacts by saying, oh, I'm
|
|
off by a second, I'll slow down or speed up to correct. Companies such as Google will
|
|
"smear" the application of a leap second over a period of hours, letting their
|
|
<a href="https://en.wikipedia.org/wiki/Unix_time">Unix time</a> counters adjust by
|
|
milliseconds, or even microseconds at a time, so that the insertion of a leap second is
|
|
virtually undetectable to applications running on the computer.
|
|
</p>
|
|
|
|
<p>
|
|
It is this de-facto standard based on
|
|
<a href="https://en.wikipedia.org/wiki/Unix_time">Unix time</a> that allows
|
|
<a href="date_v2.html"><code>date</code></a> to portably convert a count of microseconds
|
|
(for example) into field-based structures containing <code>year/month/day</code> and
|
|
<code>hours::minutes::seconds.microseconds</code>. And when you need to further convert
|
|
the <code>system_clock::time_point</code> into a local time, and/or take leap seconds into
|
|
account, the library documented herein handles it by accessing your local copy of the
|
|
<a href="http://www.iana.org/time-zones">IANA Time Zone Database</a>.
|
|
</p>
|
|
|
|
<a name="Synopsis"></a><h2>Synopsis</h2>
|
|
|
|
<p>
|
|
This synopsis provides nothing but a quick overview of the documented API of this library.
|
|
If you don't see it in the synopsis below, it is not a documented part of this library.
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
namespace date
|
|
{
|
|
|
|
using second_point = std::chrono::time_point<std::chrono::system_clock,
|
|
std::chrono::seconds>;
|
|
|
|
struct Info
|
|
{
|
|
second_point begin;
|
|
second_point end;
|
|
std::chrono::seconds offset;
|
|
std::chrono::minutes save;
|
|
std::string abbrev;
|
|
};
|
|
|
|
std::ostream&
|
|
operator<<(std::ostream& os, const Info& r);
|
|
|
|
enum class tz {utc, local};
|
|
enum class choose {earliest, latest};
|
|
|
|
class nonexistent_local_time
|
|
: public std::runtime_error
|
|
{
|
|
public:
|
|
};
|
|
|
|
class ambiguous_local_time
|
|
: public std::runtime_error
|
|
{
|
|
public:
|
|
};
|
|
|
|
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>
|
|
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);
|
|
|
|
class Link
|
|
{
|
|
public:
|
|
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);
|
|
|
|
std::ostream& operator<<(std::ostream& os, const Link& x);
|
|
|
|
class Leap
|
|
{
|
|
public:
|
|
second_point 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);
|
|
|
|
std::ostream& operator<<(std::ostream& os, const Leap& x);
|
|
|
|
template <class Duration>
|
|
bool
|
|
operator==(const Leap& x,
|
|
const std::chrono::time_point<std::chrono::system_clock, Duration>& y);
|
|
|
|
template <class Duration>
|
|
bool
|
|
operator==(const std::chrono::time_point<std::chrono::system_clock, Duration>& x,
|
|
const Leap& y);
|
|
|
|
template <class Duration>
|
|
bool
|
|
operator!=(const Leap& x,
|
|
const std::chrono::time_point<std::chrono::system_clock, Duration>& y);
|
|
|
|
template <class Duration>
|
|
bool
|
|
operator!=(const std::chrono::time_point<std::chrono::system_clock, Duration>& x,
|
|
const Leap& y);
|
|
|
|
template <class Duration>
|
|
bool
|
|
operator< (const Leap& x,
|
|
const std::chrono::time_point<std::chrono::system_clock, Duration>& y);
|
|
|
|
template <class Duration>
|
|
bool
|
|
operator< (const std::chrono::time_point<std::chrono::system_clock, Duration>& x,
|
|
const Leap& y);
|
|
|
|
template <class Duration>
|
|
bool
|
|
operator> (const Leap& x,
|
|
const std::chrono::time_point<std::chrono::system_clock, Duration>& y);
|
|
|
|
template <class Duration>
|
|
bool
|
|
operator> (const std::chrono::time_point<std::chrono::system_clock, Duration>& x,
|
|
const Leap& y);
|
|
|
|
template <class Duration>
|
|
bool
|
|
operator<=(const Leap& x,
|
|
const std::chrono::time_point<std::chrono::system_clock, Duration>& y);
|
|
|
|
template <class Duration>
|
|
bool
|
|
operator<=(const std::chrono::time_point<std::chrono::system_clock, Duration>& x,
|
|
const Leap& y);
|
|
|
|
template <class Duration>
|
|
bool
|
|
operator>=(const Leap& x,
|
|
const std::chrono::time_point<std::chrono::system_clock, Duration>& y);
|
|
|
|
template <class Duration>
|
|
bool
|
|
operator>=(const std::chrono::time_point<std::chrono::system_clock, Duration>& x,
|
|
const Leap& y);
|
|
|
|
class Rule;
|
|
|
|
struct TZ_DB
|
|
{
|
|
std::string version;
|
|
std::vector<Zone> zones;
|
|
std::vector<Link> links;
|
|
std::vector<Leap> leaps;
|
|
std::vector<Rule> rules;
|
|
};
|
|
|
|
std::ostream& operator<<(std::ostream& os, const TZ_DB& db);
|
|
|
|
const TZ_DB& get_tzdb();
|
|
const TZ_DB& reload_tzdb();
|
|
|
|
#if HAS_REMOTE_API
|
|
std::string remote_version();
|
|
bool remote_download(const std::string& version);
|
|
bool remote_install(const std::string& version);
|
|
#endif
|
|
|
|
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);
|
|
};
|
|
|
|
template <class Duration>
|
|
std::string
|
|
format(const std::locale& loc, std::string format,
|
|
std::chrono::time_point<std::chrono::system_clock, Duration> tp,
|
|
const Zone* zone = nullptr);
|
|
|
|
std::string
|
|
format(const std::locale& loc, std::string format, day_point tp,
|
|
const Zone* zone = nullptr);
|
|
|
|
template <class Duration>
|
|
std::string
|
|
format(std::string format,
|
|
std::chrono::time_point<std::chrono::system_clock, Duration> tp,
|
|
const Zone* zone = nullptr);
|
|
|
|
std::string
|
|
format(std::string format, day_point tp, const Zone* zone = nullptr);
|
|
|
|
template <class Duration>
|
|
void
|
|
parse(std::istream& is, const std::string& format,
|
|
std::chrono::time_point<std::chrono::system_clock, Duration>& tp);
|
|
|
|
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);
|
|
|
|
} // namespace date
|
|
</pre></blockquote>
|
|
|
|
<a name="Description"></a><h2>Description</h2>
|
|
|
|
<p>
|
|
Everything documented below is in <code>namespace date</code>. Explicit references to
|
|
this namespace in example code below is intentionally omitted in the hopes of reducing
|
|
verbosity.
|
|
</p>
|
|
|
|
<a name="TheDatabase"></a><h3>The Database</h3>
|
|
|
|
<p>
|
|
The database is represented with the type <code>TZ_DB</code>:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
struct TZ_DB
|
|
{
|
|
std::string version;
|
|
std::vector<Zone> zones;
|
|
std::vector<Link> links;
|
|
std::vector<Leap> leaps;
|
|
std::vector<Rule> rules;
|
|
};
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
This is a singleton class. You can get a <code>const TZ_DB&</code> to the singleton
|
|
using this function:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
const TZ_DB& get_tzdb();
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
The first call to <code>get_tzdb()</code> will initialize the database from your local
|
|
copy of the <a href="http://www.iana.org/time-zones">IANA Time Zone Database</a> located
|
|
at <code>install</code> (a file-scope variable of type <code>std::string</code> in
|
|
<a href="https://github.com/HowardHinnant/date/blob/master/tz.cpp"><code>tz.cpp</code></a>).
|
|
You will need to catch the return of this function by <code>const&</code> as the
|
|
<code>TZ_DB</code> is not constructible from a <code>const TZ_DB</code>. This can be done
|
|
with the following example code:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
auto& db = get_tzdb();
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
With a reference to the database in hand, you have read-only access to the entire
|
|
database, which is nothing more than sorted <code>vector</code>s 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:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
for (auto& z : db.zones)
|
|
std::cout << z.name() << '\n';
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
There are currently 377 zones in the database.
|
|
</p>
|
|
|
|
<p>
|
|
Or you could output the 89 <code>Link</code>s, including their <code>name()</code> and
|
|
<code>target()</code>:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
for (auto& link : db.links)
|
|
std::cout << link << '\n';
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
If you aren't happy with the format this outputs in, <code>Link</code> has public member
|
|
functions <code>name()</code> and <code>target()</code> so that you can achieve whatever
|
|
format you desire.
|
|
</p>
|
|
|
|
<p>
|
|
If needed, <code>db.version</code> is a <code>std::string</code> containing the
|
|
<a href="http://www.iana.org/time-zones">IANA Time Zone Database</a> version of the
|
|
database you are reading. For example the current version when this sentence
|
|
was written was "2016a".
|
|
</p>
|
|
|
|
<p>
|
|
You can even print the entire database out in a semi-human-readable format if desired:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
std::cout << db << '\n';
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
If you constrain the geography or history of the database during installation, those
|
|
constraints will be reflected in these examples.
|
|
</p>
|
|
|
|
<p>
|
|
If you decide you need to reload the database say, because you want to install a new
|
|
version of the <a href="http://www.iana.org/time-zones">IANA Time Zone Database</a>
|
|
without stopping your program, you can use this function:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
const TZ_DB& reload_tzdb();
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
This re-initializes the database by reading from the <code>install</code> location you
|
|
customized on installation. The use of the <code>reload_tzdb</code> function is not
|
|
pain-free, and not for every application (not for most of them I'm guessing). For example
|
|
see the <b>Thread Safety</b> section for issues related to the use of these functions.
|
|
</p>
|
|
|
|
<a name="remote"></a><h3>The remote API</h3>
|
|
|
|
<p>
|
|
The remote API is enabled only if <code>HAS_REMOTE_API</code> is set to 1 during
|
|
compilation. See <a href="#Installation">Installation</a> for more details.
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
std::string remote_version();
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
This function will query the
|
|
<a href="http://www.iana.org/time-zones">IANA Time Zone Database website</a> for the
|
|
latest version number of the IANA database, and return it as a <code>std::string</code>.
|
|
If an internet connection can not be made, an empty <code>string</code> is returned.
|
|
This string can be compared against the version of your local copy of the database:
|
|
<code>get_tzdb().version</code>.
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
bool remote_download(const std::string& version);
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
This function will attempt to download the database with the version <code>version</code>
|
|
from the <a href="http://www.iana.org/time-zones">IANA Time Zone Database website</a>.
|
|
If successful, <code>true</code> is returned and a file named
|
|
<code>version + ".tar.gz"</code> will be stored at the location <code>install</code>.
|
|
If not successful, <code>false</code> is returned.
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
bool remote_install(const std::string& version);
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
This function will attempt to uncompress the tar file downloaded by
|
|
<code>remote_download(version)</code> and replace any existing database with
|
|
the result. It will then delete the tar file. If the tar file doesn't exist,
|
|
<code>remote_install</code> will do nothing. Returns <code>true</code> on
|
|
success, else returns <code>false</code>.
|
|
</p>
|
|
|
|
<a name="Zone"></a><h3>Zone</h3>
|
|
|
|
<p>
|
|
The <code>Zone</code> class is the most important type in this library. It provides the
|
|
main access to the functionality provided by this library. Each <code>Zone</code> 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
|
|
<code>time_point</code>, which can represent either UTC or local time.
|
|
</p>
|
|
|
|
<p>
|
|
The detailed API of the <code>Zone</code> class depends upon a small amount of
|
|
infrastructure which is introduced first.
|
|
</p>
|
|
|
|
<a name="Infrastructure"></a><h4>Infrastructure</h4>
|
|
|
|
<blockquote><pre>
|
|
using second_point = std::chrono::time_point<std::chrono::system_clock,
|
|
std::chrono::seconds>;
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
<code>second_point</code> is a <code>std::chrono::time_point</code> based on
|
|
<code>system_clock</code> but with the precision of <code>seconds</code>. This library
|
|
will interoperate with <code>system_clock::time_point</code>s of <i>any</i> precision.
|
|
However the data in the database is largely based on <code>second_point</code>, and
|
|
some of the data which is presented, such as that in the <code>Info</code> class, uses
|
|
this type alias as a convenience, and to reduce verbosity. <code>second_point</code>
|
|
will implicitly convert to <code>system_clock::time_point</code>. And coarser
|
|
<code>time_point</code>s such as the <code>day_point</code> from the
|
|
<a href="date_v2.html"><code>date</code></a> library will implicitly convert to
|
|
<code>second_point</code>.
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
struct Info
|
|
{
|
|
second_point begin;
|
|
second_point end;
|
|
std::chrono::seconds offset;
|
|
std::chrono::minutes save;
|
|
std::string abbrev;
|
|
};
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
The <code>Info</code> struct is the return type of the <code>get_info</code> member
|
|
function of the <code>Zone</code> class. It contains very detailed information about the
|
|
<code>Zone</code> at the <code>time_point</code> (UTC or local) input into this member
|
|
function. <code>Info</code> contains no pointers or references into the database.
|
|
Therefore clients do not need to be concerned about holding on to <code>Info</code>s
|
|
during a call to <code>reload_tzdb()</code>. Though a call to <code>reload_tzdb()</code>
|
|
could potentially make the data in an outstanding <code>Info</code> obsolete. See
|
|
<code>Zone::get_info</code> for more details.
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
enum class tz {utc, local};
|
|
enum class choose {earliest, latest};
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
These <code>enum</code>s are used as input to some of the <code>Zone</code> member
|
|
functions. <code>tz::utc</code> indicates that a <code>time_point</code> represents a
|
|
time in the UTC time zone. <code>tz::local</code> indicates that a
|
|
<code>time_point</code> represents a time in the <code>Zone</code>'s local time zone.
|
|
The <code>choose enum</code> 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.
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
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;
|
|
};
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
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
|
|
<code>what()</code> member function that will contain a very detailed explanation
|
|
including specific times for the specific <code>time_point</code>s involved in the
|
|
attempted mapping.
|
|
</p>
|
|
|
|
<p>
|
|
If in a call to <code>Zone::to_sys</code> the local <code>time_point</code> falls into a
|
|
"gap" for which no local time exists, a <code>nonexistent_local_time</code> exception is
|
|
thrown.
|
|
</p>
|
|
|
|
<p>
|
|
If in a call to <code>Zone::to_sys</code> the local <code>time_point</code> has an
|
|
ambiguous mapping to UTC, a <code>ambiguous_local_time</code> exception is thrown.
|
|
</p>
|
|
|
|
<p>
|
|
Either exceptional situation can be circumvented with the use of
|
|
<code>choose::earliest</code> or <code>choose::latest</code> in the call to
|
|
<code>to_sys</code>.
|
|
</p>
|
|
|
|
<a name="ZoneContinued"></a><h4>Zone continued</h4>
|
|
|
|
<blockquote><pre>
|
|
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>
|
|
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);
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
The entire public API of the <code>Zone</code> is <code>const</code>. Once the database
|
|
is initialized (or reloaded), <code>Zone</code>s are set in concrete.
|
|
</p>
|
|
|
|
<hr>
|
|
|
|
<p>
|
|
The current time zone associated with your computer can be retrieved with the namespace
|
|
scope function <code>current_zone()</code>. For example:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
std::cout << current_zone()->name() << '\n';
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
For me the above currently outputs <code>America/New_York</code>.
|
|
</p>
|
|
|
|
<hr>
|
|
|
|
<blockquote><pre>
|
|
const Zone* locate_zone(const std::string& tz_name);
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
<code>locate_zone</code> returns a pointer to a <code>Zone</code> in the database
|
|
associated with <code>tz_name</code>. If it can't find a <code>Zone</code> named
|
|
<code>tz_name</code>, the implementation will search for a <code>Link</code> named
|
|
<code>tz_name</code>, and then return the <code>Zone</code> associated with the
|
|
<code>Link</code>'s <code>target()</code>. If <code>tz_name</code> can not be found in the
|
|
database, a <code>std::runtime_error</code> is thrown.
|
|
</p>
|
|
|
|
<p>
|
|
Example:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
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';
|
|
}
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
Which outputs:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
Europe/London
|
|
Europe/London
|
|
Europe/New_Jersey not found in timezone database
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
Note that <code>locate_zone</code> never returns <code>nullptr</code>. Also note that
|
|
the first call to <code>locate_zone</code> may implicitly initialize the database.
|
|
</p>
|
|
|
|
<hr>
|
|
|
|
<blockquote><pre>
|
|
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;
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
<code>to_local</code> maps a <code>system_clock</code>-associated <code>time_point</code>
|
|
from UTC to local time, returning both the mapped <code>time_point</code> and an
|
|
abbreviation for the local time zone. This member function accepts any precision
|
|
<code>time_point</code>, but returns a <code>time_point</code> with a precision of
|
|
<code>seconds</code> or finer. This is done because it is possible that some of the
|
|
mappings returned by the database need the precision of a second.
|
|
</p>
|
|
|
|
<p>
|
|
There are only two ways this function can fail:
|
|
</p>
|
|
|
|
<ol>
|
|
<li><p>
|
|
Out of memory error. Not bloody likely. The only memory that possibly could be
|
|
allocated is for the abbreviation stored in a <code>std::string</code> and all known
|
|
implementations will fit all known abbreviations into their short string buffer.
|
|
</p></li>
|
|
<li><p>
|
|
If you curtailed history during installation, a <code>runtime_error</code> will be thrown
|
|
if <code>tp</code> refers to a <code>time_point</code> outside of the range
|
|
<code>min_year/jan/1 00:00:00</code> to <code>max_year/dec/31 23:59:59</code>. This can
|
|
not happen with the default settings of <code>min_year</code> and <code>max_year</code>.
|
|
</p></li>
|
|
</ol>
|
|
|
|
<p>
|
|
Example:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
auto local = current_zone()->to_local(system_clock::now());
|
|
cout << local.first << ' ' << local.second << '\n';
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
Which just output for me:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
2015-07-12 16:57:14.430467 EDT
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
Not quite 5pm in the US Eastern timezone during daylight saving time.
|
|
</p>
|
|
|
|
<p>
|
|
And for a historical example:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
auto distant_past = locate_zone("America/New_York")->to_local(day_point(feb/9/1942) + 7h);
|
|
cout << distant_past.first << ' ' << distant_past.second << '\n';
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
Which outputs:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
1942-02-09 03:00:00 EWT
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
The US shifted to "War Time."
|
|
</p>
|
|
|
|
<hr>
|
|
|
|
<p>
|
|
If you want to go the other direction (from local time to UTC) use:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
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;
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
For example:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
auto distant_past = locate_zone("America/New_York")->to_sys(day_point(feb/9/1942) + 3h);
|
|
cout << distant_past << ' ' << " UTC\n";
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
Which outputs:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
1942-02-09 07:00:00 UTC
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
This function will throw an exception of type <code>nonexistent_local_time</code> 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.
|
|
</p>
|
|
|
|
<p>
|
|
For example:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
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';
|
|
}
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
Which outputs:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
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
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
And sometimes a local time can be ambiguous, mapping to more than one UTC time:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
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';
|
|
}
|
|
</pre></blockquote>
|
|
|
|
<blockquote><pre>
|
|
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
|
|
</pre></blockquote>
|
|
|
|
<hr>
|
|
|
|
<p>
|
|
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:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
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";
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
Which outputs:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
1945-09-30 05:59:59.999999999 UTC
|
|
1945-09-30 06:59:59.999999999 UTC
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
When using this form of <code>to_sys</code> and the local time is non-existent, both
|
|
choices will map to the single UTC time on either side of the gap:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
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";
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
Which outputs:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
1942-02-09 07:00:00.000 UTC
|
|
1942-02-09 07:00:00.000 UTC
|
|
</pre></blockquote>
|
|
|
|
<hr>
|
|
|
|
<p>
|
|
So far I've shown how given a <code>Zone</code> and a <code>system_clock::time_point</code>
|
|
of arbitrary precision, you can use <code>to_local</code> to map UTC to local time, and
|
|
<code>to_sys</code> 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?
|
|
</p>
|
|
|
|
<p>
|
|
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 <code>get_info</code> can
|
|
tell you how far into the past, and far into the future a given offset and abbreviation
|
|
are guaranteed to stay valid:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
template <class Rep, class Period>
|
|
Info
|
|
get_info(std::chrono::time_point<std::chrono::system_clock,
|
|
std::chrono::duration<Rep, Period>> tp,
|
|
tz timezone) const;
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
Input a <code>time_point tp</code>, and indicate whether <code>tp</code> represents a UTC
|
|
<code>time_point</code> (<code>tz::utc</code>) or a local <code>time_point</code>
|
|
(<code>tz::local</code>), and a struct <code>Info</code> for that <code>time_point</code>
|
|
is returned:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
auto info = locate_zone("America/New_York")->get_info(system_clock::now(), tz::utc);
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
Upon return <code>info</code> will contain the following information:
|
|
</p>
|
|
|
|
<ul>
|
|
<li><p>
|
|
<code>info.offset</code> has type <code>std::chrono::seconds</code> and indicates the
|
|
current offset from UTC. A positive offset indicates that local time is ahead of UTC and
|
|
a negative offset indicates that local time is behind UTC.
|
|
</p></li>
|
|
|
|
<li><p>
|
|
<code>info.abbrev</code> has type <code>std::string</code> and indicates the
|
|
current abbreviation for the local time zone.
|
|
</p></li>
|
|
|
|
<li><p>
|
|
<code>info.begin</code> has type <code>second_point</code> and indicates the first
|
|
instant guaranteed to have this same <code>offset</code> and <code>abbrev</code>. The
|
|
<code>time_point info.begin</code> is implicitly in the UTC time zone. Note that it is
|
|
possible that the instant prior to <code>begin</code> may or may not have a
|
|
different <code>offset</code> or <code>abbrev</code>.
|
|
</p></li>
|
|
|
|
<li><p>
|
|
<code>info.end</code> has type <code>second_point</code> and indicates the last instant
|
|
before which it is guaranteed to have this same <code>offset</code> and
|
|
<code>abbrev</code>. The <code>time_point info.end</code> is implicitly in the UTC time
|
|
zone. The <code>offset</code> and <code>abbrev</code> associated with
|
|
<code>info.end</code> and beyond may or may not be different.
|
|
</p></li>
|
|
|
|
<li><p>
|
|
<code>info.save</code> has type <code>std::chrono::minutes</code> and indicates the
|
|
amount of time that daylight savings time has moved the current offset. This can be
|
|
used to detect whether or not daylight savings is in effect (no if the value is 0min).
|
|
Note that <code>save</code> is already incorporated into the value of <code>offset</code>,
|
|
so you don't have to look at this field to get the current offset. This field exists
|
|
just in the spirit of more information is better.
|
|
</p></li>
|
|
</ul>
|
|
|
|
<p>
|
|
The <code>Info</code> also has a streaming operator which is mainly useful for debugging
|
|
purposes. Here is sample code and output:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
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
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
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 <code>to_sys</code>: by calling <code>get_info</code> and
|
|
analyzing how the input time relates to <code>begin</code> and <code>end</code>.
|
|
</p>
|
|
|
|
<p>
|
|
Additionally the <code>Zone</code> is equality and less-than comparable (using the
|
|
<code>name()</code>). And you can stream the <code>Zone</code> 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.
|
|
</p>
|
|
|
|
<a name="flightexample1"></a><h3>Flight Example</h3>
|
|
|
|
<p>
|
|
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.
|
|
</p>
|
|
|
|
<p>
|
|
Given that the departure is one minute past noon on Dec. 30, 1978, local time, what is
|
|
the local arrival time?
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
#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';
|
|
}
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
There are several points to be made about the above code:
|
|
</p>
|
|
|
|
<ul>
|
|
<li><p>
|
|
A stylistic guide is to use "<code>sys</code>" for <code>system_clock time_points</code>.
|
|
This helps distinguish system times (UTC) from local times.
|
|
</p></li>
|
|
|
|
<li><p>
|
|
No time arithmetic is done using local <code>time_point</code>s. All time arithmetic is
|
|
done in the UTC time zone. Time arithmetic in terms of local <code>time_point</code>s is
|
|
error prone. Note though that this computation (using
|
|
<code>system_clock::time_point</code>) is ignorant of leap seconds. If you must, see how
|
|
to take leap seconds into account with <b>utc_clock</b>.
|
|
</p></li>
|
|
|
|
<li><p>
|
|
There is no <code>using namespace std</code> because "<code>dec</code>" is ambiguous if
|
|
both <code>date</code> and <code>std</code> are brought into scope. In <code>date</code>
|
|
"<code>dec</code>" means December. In <code>std</code> "<code>dec</code>" means:
|
|
</p>
|
|
<blockquote><pre>
|
|
ios_base& dec(ios_base& str);
|
|
</pre></blockquote>
|
|
<p>
|
|
Thank goodness for namespaces!
|
|
</p></li>
|
|
</ul>
|
|
|
|
<p>
|
|
The output of the above program is:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
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
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
And this program is <i>exactly correct</i>. But what happens with the same flight on
|
|
the following day?
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
auto nyc_departure_sys = nyc_tz->to_sys(day_point(dec/<b>31</b>/1978) + 12h + 1min);
|
|
|
|
departure NYC time: 1978-12-31 12:01:00 EST
|
|
flight time is 14:44
|
|
arrival Tehran time: 1979-01-01 <b>11:15:00</b> IRST
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
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 <b>Flight Example with leap seconds</b>.
|
|
</p>
|
|
|
|
<a name="utc_clock"></a><h3>utc_clock</h3>
|
|
|
|
<p>
|
|
One of the first questions everyone asks when a new date-time library comes out is:
|
|
</p>
|
|
|
|
<blockquote><p>
|
|
Does it handle leap seconds?
|
|
</p></blockquote>
|
|
|
|
<p>
|
|
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:
|
|
</p>
|
|
|
|
<ul>
|
|
<li><p>
|
|
<code>system_clock</code> (<a href="https://en.wikipedia.org/wiki/Unix_time">Unix
|
|
time</a>) <i>sort of</i> handles leaps seconds in that "now" in
|
|
<a href="https://en.wikipedia.org/wiki/Unix_time">Unix
|
|
time</a> <i>always</i> corresponds to "now" in UTC (UTC being leap second aware). It is
|
|
just that the difference between two
|
|
<a href="https://en.wikipedia.org/wiki/Unix_time">Unix time</a> <code>time_point</code>s
|
|
may produce a number of <code>std::chrono::seconds</code> which does not reflect the
|
|
exact number of physical seconds which has actually transpired.
|
|
</p></li>
|
|
|
|
<li><p>
|
|
Unless you are using a very special computer, directly connected to an atomic clock, your
|
|
computer is likely using <a href="https://en.wikipedia.org/wiki/Unix_time">Unix time</a>.
|
|
If you want to correctly interpret things such as time stamps, and you want to correctly
|
|
handle leap seconds, it is critical to know if those time stamps (the input data) were
|
|
generated by software that correctly handled leap seconds. Odds are very good that they
|
|
were generated by software following
|
|
<a href="https://en.wikipedia.org/wiki/Unix_time">Unix time</a>, or
|
|
<a href="http://www.ntp.org">Network Time Protocol</a> which for our purposes here, treats
|
|
leaps seconds in essentially the same way
|
|
<a href="https://en.wikipedia.org/wiki/Unix_time">Unix time</a> does (as a clock
|
|
correction).
|
|
</p></li>
|
|
|
|
<li><p>
|
|
Handling leap seconds is not free. Don't try to just so you can be "more exact." Do it
|
|
when your requirements actually demand it, and you have the resources to test that you are
|
|
indeed correctly handling leap seconds. If the person telling you to correctly handle
|
|
leap seconds has <a href="http://dilbert.com">pointy hair</a>, double down on your
|
|
testing, and have your atomic clock ready. The extra expense is not so much memory or
|
|
performance (those penalties exist but are relatively small), but in the problem of
|
|
believing you've achieved more accuracy when you actually haven't.
|
|
</p></li>
|
|
|
|
</ul>
|
|
|
|
<p>
|
|
<code>utc_clock</code> is a <code>std::chrono</code>-conforming clock with the same
|
|
<code>duration</code> as your <code>system_clock</code>, and a <code>now()</code>
|
|
function that returns the actual number of physical seconds since 1970-01-01 00:00:00 UTC
|
|
(counting leap seconds):
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
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);
|
|
};
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
Additionally <code>utc_clock</code> has static member functions for converting between
|
|
<code>utc_clock</code>-based <code>time_point</code>s to and from
|
|
<code>system_clock</code>-based <code>time_point</code>s of any precision. But it is
|
|
important to remember that <code>utc_clock</code> isn't connected to a super accurate
|
|
atomic clock. All it does is look its <code>time_point</code> 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 <code>utc_clock::now()</code> function simply calls
|
|
<code>system_clock::now()</code> and adds the current total of leaps seconds (currently
|
|
26) to the result. This is useful behavior but it is important to understand that
|
|
<code>utc_clock</code> is not a highly accurate scientific instrument. It is precisely
|
|
as accurate as your existing <code>std::chrono::system_clock</code>.
|
|
</p>
|
|
|
|
<a name="flightexample2"></a><h3>Flight Example with leap seconds</h3>
|
|
|
|
<p>
|
|
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 <code>dec/31/1978</code>
|
|
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)?
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
#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);
|
|
<b>auto nyc_departure_utc = utc_clock::sys_to_utc(nyc_departure_sys);
|
|
</b>auto flight_length = 14h + 44min;<b>
|
|
auto teh_arrival_utc = nyc_departure_utc + flight_length;
|
|
auto teh_arrival_sys = utc_clock::utc_to_sys(teh_arrival_utc);</b>
|
|
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 <b>11:14:59</b> IRST
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
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 <code>utc_clock</code>-based
|
|
<code>time_point</code>s, instead of <code>system_clock</code>-based
|
|
<code>time_point</code>s. Just convert to <code>system_clock</code> 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 <code>time_point</code>. In this example, the only time arithmetic is:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
auto teh_arrival_utc = nyc_departure_utc + flight_length;
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
The reset of the code is simply about converting from local, to <code>system_clock</code>
|
|
to <code>utc_clock</code> and back.
|
|
</p>
|
|
|
|
<blockquote><p>
|
|
<i>Digression:</i> 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:
|
|
</p>
|
|
<blockquote><p>
|
|
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.
|
|
</p></blockquote>
|
|
<p>
|
|
Subsequently I wrote the following code to search the entire planet, and the last 45
|
|
years, to find these rare chronological events:
|
|
</p>
|
|
<blockquote><pre>
|
|
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';
|
|
}
|
|
}
|
|
}
|
|
</pre></blockquote>
|
|
<p>
|
|
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.
|
|
</p>
|
|
<p>
|
|
You too can analyze the <a href="http://www.iana.org/time-zones">IANA Time Zone
|
|
Database</a> in creative and interesting ways no one else has thought of. There is a lot
|
|
of history here.
|
|
</p></blockquote>
|
|
|
|
<a name="Formatting"></a><h3>Formatting</h3>
|
|
|
|
<p>
|
|
All of the types in this library, as well as in
|
|
<a href="date_v2.html"><code>date.h</code></a> 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 <code>time_put<char></code>
|
|
facet. <code>time_put<char></code> itself is built on C's <code>strftime</code>
|
|
function. But <code>time_put<char></code> is sensitive to C++ <code>locale</code>s.
|
|
</p>
|
|
|
|
<p>
|
|
The basic way to use formatting is to call the <code>format</code> function like this:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
cout << format("%A %F %T", floor<seconds>(system_clock::now())) << '\n';
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
Which just output for me:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
Sunday 2016-04-03 22:02:19
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
Note the cast to <code>seconds</code> precision in the call. This is how you control
|
|
the precision of the <code>seconds</code> output (if any). The modifiers <code>%S</code>
|
|
and <code>%T</code> will output seconds to whatever the precision is of the
|
|
<code>time_point</code>. For example:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
cout << format("%A %F %T", floor<milliseconds>(system_clock::now())) << '\n';
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
would instead output:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
Sunday 2016-04-03 22:02:19.656
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
Note that there is an implicit time zone being used here: UTC. The <code>%z</code> and
|
|
<code>%Z</code> modifiers can be used to show this:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
cout << format("%A %F %T %z %Z", floor<milliseconds>(system_clock::now())) << '\n';
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
would instead output:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
Sunday 2016-04-03 22:02:19.656 +0000 UTC
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
A <code>Zone</code> can also be passed in and then the <code>%z</code> and
|
|
<code>%Z</code> modifiers will reflect that passed-in zone. It is important to
|
|
remember however that <code>format</code> <em>never</em> shifts the
|
|
<code>time_point</code> for you. Instead you pass in a <code>Zone</code> that you know
|
|
to be associated with your <code>time_point</code>. For example:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
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';
|
|
</pre></blockquote>
|
|
|
|
<blockquote><pre>
|
|
Monday 2016-04-04 00:02:19.656 +0200 CEST
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
The <i>only</i> thing <code>format</code> ever does with a <code>Zone</code> is extract
|
|
the offset and/or the abbreviation for use with the <code>%z</code> and <code>%Z</code>
|
|
modifiers.
|
|
</p>
|
|
|
|
<p>
|
|
You can also pass in a <code>locale</code> to <code>format</code>:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
cout << format(locale("de_DE"), "%A %F %T %z %Z", local, zone) << '\n';
|
|
</pre></blockquote>
|
|
|
|
<blockquote><pre>
|
|
Montag 2016-04-04 00:02:19,656 +0200 CEST
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
The set of named locales that your OS supports is defined by your OS, not this library.
|
|
</p>
|
|
|
|
<p>
|
|
Instead of a <code>time_point</code> you can also pass in anything that is implicitly
|
|
convertible to <code>day_point</code>:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
cout << format(locale("de_DE"), "%A %B %e, %Y", 2016_y/jul/mon[1]) << '\n';
|
|
</pre></blockquote>
|
|
|
|
<blockquote><pre>
|
|
Montag Juli 4, 2016
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
In summary, use <code>format</code> by passing in a format string and a
|
|
<code>time_point</code>, or something implicitly convertible to a <code>day_point</code>.
|
|
You can optionally pass in a <code>locale</code> as the first parameter, and a
|
|
<code>Zone</code> as the last parameter. <code>format</code> will never alter the value
|
|
of your <code>time_point</code>. The precision of the <code>time_point</code> controls
|
|
the precision of seconds with the <code>%S</code> and <code>%T</code> modifiers. If you
|
|
pass in a <code>Zone</code>, this will only impact the output of <code>%z</code> and
|
|
<code>%Z</code> (which default to <code>+0000</code> and <code>UTC</code> respectively).
|
|
The output of <code>format</code> is a <code>std::string</code>.
|
|
</p>
|
|
|
|
<a name="Parsing"></a><h3>Parsing</h3>
|
|
|
|
<p>
|
|
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.
|
|
</p>
|
|
|
|
<p>
|
|
However this section introduces a <code>parse</code> function which is built on top
|
|
of the C++11 <code>time_get</code> facet which can also be used:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
template <class Duration>
|
|
void
|
|
parse(std::istream& is, const std::string& format,
|
|
std::chrono::time_point<std::chrono::system_clock, Duration>& tp);
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
You can input any <code>istream</code>, and a format string much like that used for
|
|
<code>format</code> and <code>strftime</code>, and a <code>time_point</code> of any
|
|
precision, and this function will attempt to extract the <code>time_point</code> from
|
|
the <code>istream</code> by using the format <code>string</code>. If not successful,
|
|
the <code>time_point</code> will not be altered.
|
|
</p>
|
|
|
|
<p>
|
|
Example use:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
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';
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
Which outputs:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
2016-04-03 22:02:19.656000
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
Note that the <code>locale</code> associated with the <code>istream</code> is respected.
|
|
If the format string contains a <code>%z</code> which matches the input stream, this is
|
|
used to convert the value to UTC. If there is no <code>%z</code>, then no conversion
|
|
happens (you can assume whatever timezone you want). Note that fractional seconds are
|
|
accepted as long as one uses <code>%T</code> or <code>%S</code>, and the precision of the
|
|
<code>time_point</code> is fine enough to accept fractional seconds.
|
|
</p>
|
|
|
|
<p>
|
|
<code>%Z</code> is not accepted as the mapping from a timezone abbreviation to UTC is
|
|
in general, ambiguous. If you have a <code>%Z</code> in the format string, this will
|
|
result in <code>is.fail()</code> returning <code>true</code> after the call to
|
|
<code>parse</code>.
|
|
</p>
|
|
|
|
<p>
|
|
However, if you absolutely must parse a timestamp with a timezone abbreviation in it,
|
|
an extra <code>parse</code> overload is provided:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
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);
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
Now if <code>%Z</code> matches a word in <code>is</code> <em>and</em> if the rest of
|
|
<code>is</code> correctly parses according to <code>format</code>, then
|
|
<code>abbrev</code> will be assigned the word which matched <code>%Z</code>. This
|
|
<i>will not</i> have any impact on the value of <code>tp</code> (no timezone offset
|
|
applied). However perhaps there is enough a-priori knowledge in your application to
|
|
make use of the value of <code>abbrev</code> to correctly interpret the meaning of
|
|
the timestamp and the resulting value of <code>tp</code>.
|
|
</p>
|
|
|
|
<p>
|
|
As an example of how this option can be both useful <i>and</i> 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.
|
|
</p>
|
|
|
|
<p>
|
|
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.
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
#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';
|
|
}
|
|
}
|
|
}
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
This program outputs:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
2016-04-07 01:45:28 UTC Australia/Brisbane
|
|
Australia/Currie
|
|
Australia/Hobart
|
|
Australia/Lindeman
|
|
Australia/Melbourne
|
|
Australia/Sydney
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
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.
|
|
</p>
|
|
|
|
<p>
|
|
And this is a good case. Consider just altering the abbreviation in the above example
|
|
from AEST to BST. Now the output is:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
2016-04-07 10:45:28 UTC Europe/London
|
|
2016-04-07 00:45:28 UTC Pacific/Bougainville
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
Meaning: Not only do we not know what timezone this refers to, it could mean one of
|
|
two different UTC timepoints!
|
|
</p>
|
|
|
|
<p>
|
|
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.
|
|
</p>
|
|
|
|
<a name="ThreadSafety"></a><h3>Thread Safety</h3>
|
|
|
|
<p>
|
|
The implementation of the database is as a singleton. An application can have only a
|
|
single database. However the initial construction of that database is thread safe, and
|
|
subsequent access is always const (unless you need to update to a new version of the
|
|
database at run time). The initial construction thread safety is implemented with C++11
|
|
function local statics. If your compiler does not implement threadsafe function local
|
|
statics, then you will need to arrange for your own thread safety during the
|
|
initialization stage.
|
|
</p>
|
|
|
|
<p>
|
|
If you need to update the database at runtime via the <code>reload_tzdb</code> function,
|
|
you will need to provide your own thread safety if you are in a multithreaded application.
|
|
This could be done (for example) by ensuring that all of your calls into the database
|
|
occur with the permission of a C++14
|
|
<code>std::shared_lock<std::shared_timed_mutex></code>, except for the calls to
|
|
<code>reload_tzdb</code>, which must be done with a
|
|
<code>std::unique_lock<std::shared_timed_mutex></code>. Such facilities are not
|
|
provided by default with this library so that you don't pay for them if you don't need
|
|
them.
|
|
</p>
|
|
|
|
<p>
|
|
In case you do need to protect calls into the database with a mutex, here is a
|
|
comprehensive list of the public API that accesses the database:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
<b><i>// Read/write access</i></b>
|
|
const TZ_DB& reload_tzdb();
|
|
|
|
<b><i>// Read-only access</i></b>
|
|
|
|
const TZ_DB& get_tzdb();
|
|
|
|
<i>// Zone functions</i>
|
|
const Zone* locate_zone(const std::string& tz_name);
|
|
const Zone* current_zone();
|
|
Info Zone::get_info(std::chrono::system_clock::time_point tp, tz timezone) const;
|
|
|
|
template <class Rep, class Period>
|
|
auto
|
|
Zone::to_local(std::chrono::time_point<std::chrono::system_clock,
|
|
std::chrono::duration<Rep, Period>> tp) const
|
|
-> std::pair<decltype(tp + get_info(tp, tz::utc).offset), std::string>;
|
|
|
|
template <class Rep, class Period>
|
|
auto
|
|
Zone::to_sys(std::chrono::time_point<std::chrono::system_clock,
|
|
std::chrono::duration<Rep, Period>> tp) const
|
|
-> decltype(tp - get_info(tp, tz::local).offset);
|
|
|
|
template <class Rep, class Period>
|
|
auto
|
|
Zone::to_sys(std::chrono::time_point<std::chrono::system_clock,
|
|
std::chrono::duration<Rep, Period>> tp, choose z) const
|
|
-> decltype(tp - get_info(tp, tz::local).offset);
|
|
|
|
<i>// leap second functions</i>
|
|
utc_clock::time_point utc_clock::now() noexcept;
|
|
|
|
template <class Duration>
|
|
std::chrono::time_point<utc_clock,
|
|
typename std::common_type<Duration, std::chrono::seconds>::type>
|
|
utc_clock::sys_to_utc(std::chrono::time_point<std::chrono::system_clock, Duration> t);
|
|
|
|
template <class Duration>
|
|
std::chrono::time_point<std::chrono::system_clock,
|
|
typename std::common_type<Duration, std::chrono::seconds>::type>
|
|
utc_clock::utc_to_sys(std::chrono::time_point<utc_clock, Duration> t);
|
|
</blockquote></pre>
|
|
|
|
<p>
|
|
Those functions that explicitly return const references or const pointers (the first five)
|
|
continue to have read access into the database after the call until the client drops that
|
|
reference or pointer.
|
|
</p>
|
|
|
|
<p>
|
|
<b>Emphasis:</b> If you don't use the <code>reload_tzdb</code> function, then all access
|
|
is <i>read-only</i> and there is no need for synchronization with an external mutex.
|
|
</p>
|
|
|
|
<a name="Installation"></a><h2>Installation</h2>
|
|
|
|
<p>
|
|
There are only three files in the timezone library:
|
|
<a href="https://github.com/HowardHinnant/date/blob/master/tz.h"><code>tz.h</code></a>,
|
|
<a href="https://github.com/HowardHinnant/date/blob/master/tz_private.h"><code>tz_private.h</code></a> and
|
|
<a href="https://github.com/HowardHinnant/date/blob/master/tz.cpp"><code>tz.cpp</code></a>.
|
|
These are sources located at the github repository
|
|
<a href="https://github.com/HowardHinnant/date">https://github.com/HowardHinnant/date</a>.
|
|
The source
|
|
<a href="https://github.com/HowardHinnant/date/blob/master/tz.cpp"><code>tz.cpp</code></a>
|
|
contains the following string near the top:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
static std::string install{"~/Downloads/tzdata"}; // "c:\\tzdata" on Windows
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
You should set this such that <code>install</code> points to the directory
|
|
where your library or application can find the downloaded and uncompressed
|
|
<a href="http://www.iana.org/time-zones">IANA Time Zone Database</a>.
|
|
</p>
|
|
|
|
<p>
|
|
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.
|
|
</p>
|
|
|
|
<blockquote>
|
|
<table cellpadding="5">
|
|
<tr>
|
|
<td><code>HAS_REMOTE_API</code></td>
|
|
<td>Defaults to 1 on Linux and OS X, and to 0 on Windows</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>AUTO_DOWNLOAD</code></td>
|
|
<td>Defaults to <code>HAS_REMOTE_API</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>LAZY_INIT</code></td>
|
|
<td>Defaults to 1</td>
|
|
</tr>
|
|
</table>
|
|
</blockquote>
|
|
|
|
<p>
|
|
If <code>HAS_REMOTE_API</code> is 1 then the <a href="#remote">remote API</a> exists,
|
|
else it doesn't:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
std::string remote_version();
|
|
bool remote_download(const std::string& version);
|
|
bool remote_install(const std::string& version);
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
The remote API requires linking against <code>libcurl</code>
|
|
(<a href="https://curl.haxx.se/libcurl">https://curl.haxx.se/libcurl</a>).
|
|
On OS X and Linux this is done with <code>-lcurl</code>.
|
|
<code>libcurl</code> comes pre-installed on OS X and Linux, but not on Windows.
|
|
However one can download it for Windows.
|
|
</p>
|
|
|
|
<p>
|
|
If <code>AUTO_DOWNLOAD</code> 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.
|
|
</p>
|
|
|
|
<p>
|
|
If <code>AUTO_DOWNLOAD</code> is 1 then <code>reload_tzdb()</code> will automatically
|
|
check <code>remote_version()</code> against the local version, and if unchanged, will do
|
|
nothing. If <code>remote_version()</code> can't access the IANA website,
|
|
<code>reload_tzdb()</code> will re-initialize the database anyway (perhaps you changed it
|
|
manually because the net is down). If the <code>remote_version()</code> is confirmed to
|
|
have changed (and <code>AUTO_DOWNLOAD</code> is on), then <code>reload_tzdb()</code> will
|
|
download and install the latest version.
|
|
</p>
|
|
|
|
<p>
|
|
If <code>AUTO_DOWNLOAD</code> is off, <code>reload_tzdb()</code> re-initializes the
|
|
database without using the remote API (presumably because it has been manually updated).
|
|
</p>
|
|
|
|
<p>
|
|
If <code>HAS_REMOTE_API</code> is off and <code>AUTO_DOWNLOAD</code> is on, there will be
|
|
a compile-time error as <code>AUTO_DOWNLOAD</code> needs the remote API.
|
|
</p>
|
|
|
|
<p>
|
|
If <code>LAZY_INIT</code> is on, the <code>Zone</code>s are not fully compiled upon first
|
|
access to the database. As each <code>Zone</code> is accessed individaully by the
|
|
programmer (when they are used), they are fully compiled at that point. However, this
|
|
further <code>Zone</code> compilation does not involve any access to the local copy of the
|
|
tz database files.
|
|
</p>
|
|
|
|
<p>
|
|
If <code>LAZY_INIT</code> is off, every <code>Zone</code> is fully compiled upon first
|
|
access to the database.
|
|
</p>
|
|
|
|
<p>
|
|
<code>LAZY_INIT</code> speeds up the initialization of the database, but slows down the
|
|
first use of any individual <code>Zone</code>. If you are only using a few
|
|
<code>Zone</code>s then <code>LAZY_INIT</code> is a clear win. If you are immediately
|
|
using all of the <code>Zone</code>s (say for some database analysis) then
|
|
<code>LAZY_INIT</code> is not a win.
|
|
</p>
|
|
|
|
<p>
|
|
If <code>LAZY_INIT</code> 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:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
int
|
|
main()
|
|
{
|
|
std::thread(date::get_tzdb).detach();
|
|
// other initialization ...
|
|
}
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
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.
|
|
</p>
|
|
|
|
<p>
|
|
If you would like to trade off functionality for size, you can reduce the size of the
|
|
database in two ways:
|
|
</p>
|
|
|
|
<ul>
|
|
<li>Limit geography.</li>
|
|
<li>Limit history.</li>
|
|
</ul>
|
|
|
|
<p>
|
|
You can limit geography by removing one or more of the files in this list:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
const std::vector<const std::string> files =
|
|
{
|
|
"africa", "antarctica", "asia", "australasia", "backward", "etcetera", "europe",
|
|
"pacificnew", "northamerica", "southamerica", "systemv", "leapseconds"
|
|
};
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
You can limit history by setting <code>min_year</code> to something more recent such as:
|
|
</p>
|
|
|
|
<blockquote><pre>
|
|
CONSTDATA auto min_year = 2015_y;
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
When you do so, if you ask to convert a date prior to <code>min_year</code>, an exception
|
|
will be thrown.
|
|
</p>
|
|
|
|
<p>
|
|
The entire database consumes about 859Kb.
|
|
</p>
|
|
|
|
<p>
|
|
Compile
|
|
<a href="https://github.com/HowardHinnant/date/blob/master/tz.cpp"><code>tz.cpp</code></a>
|
|
in with the rest of your library or application.
|
|
</p>
|
|
|
|
<p>
|
|
If <code>AUTO_DOWNLOAD</code> is not enabled, you are responsible for keeping your
|
|
<a href="http://www.iana.org/time-zones">IANA Time Zone Database</a> 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.
|
|
</p>
|
|
|
|
<p>
|
|
There is no preprocessing of the
|
|
<a href="http://www.iana.org/time-zones">IANA Time Zone Database</a> required. This
|
|
library efficiently initializes itself directly from the files of the
|
|
<a href="http://www.iana.org/time-zones">IANA Time Zone Database</a>. If your application
|
|
is long running, and you anticipate the need to update the
|
|
<a href="http://www.iana.org/time-zones">IANA Time Zone Database</a> to a new version
|
|
without recompiling, or even restarting your application, there is API to accomplish
|
|
that.
|
|
</p>
|
|
|
|
<p>
|
|
If you constrain geography during installation, this can significantly reduce the time
|
|
it takes to initialize the database. However constraining history has little (if any)
|
|
impact on the performance of the initialization.
|
|
</p>
|
|
|
|
<a name="Acknowledgements"></a><h2>Acknowledgements</h2>
|
|
|
|
<p>
|
|
A database parser is nothing without its database. I would like to thank the founding
|
|
contributor of the <a href="http://www.iana.org/time-zones">IANA Time Zone Database</a>
|
|
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.
|
|
</p>
|
|
|
|
</body>
|
|
</html>
|