diff common/date/tz.cpp @ 0:a4671277546c tip

created the repository for the thymian project
author ferencd
date Tue, 17 Aug 2021 11:19:54 +0200
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/date/tz.cpp	Tue Aug 17 11:19:54 2021 +0200
@@ -0,0 +1,3821 @@
+// The MIT License (MIT)
+//
+// Copyright (c) 2015, 2016, 2017 Howard Hinnant
+// Copyright (c) 2015 Ville Voutilainen
+// Copyright (c) 2016 Alexander Kormanovsky
+// Copyright (c) 2016, 2017 Jiangang Zhuang
+// Copyright (c) 2017 Nicolas Veloz Savino
+// Copyright (c) 2017 Florian Dang
+// Copyright (c) 2017 Aaron Bishop
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+// Our apologies.  When the previous paragraph was written, lowercase had not yet
+// been invented (that would involve another several millennia of evolution).
+// We did not mean to shout.
+
+#ifdef _WIN32
+   // windows.h will be included directly and indirectly (e.g. by curl).
+   // We need to define these macros to prevent windows.h bringing in
+   // more than we need and do it early so windows.h doesn't get included
+   // without these macros having been defined.
+   // min/max macros interfere with the C++ versions.
+#  ifndef NOMINMAX
+#    define NOMINMAX
+#  endif
+   // We don't need all that Windows has to offer.
+#  ifndef WIN32_LEAN_AND_MEAN
+#    define WIN32_LEAN_AND_MEAN
+#  endif
+
+   // for wcstombs
+#  ifndef _CRT_SECURE_NO_WARNINGS
+#    define _CRT_SECURE_NO_WARNINGS
+#  endif
+
+   // None of this happens with the MS SDK (at least VS14 which I tested), but:
+   // Compiling with mingw, we get "error: 'KF_FLAG_DEFAULT' was not declared in this scope."
+   // and error: 'SHGetKnownFolderPath' was not declared in this scope.".
+   // It seems when using mingw NTDDI_VERSION is undefined and that
+   // causes KNOWN_FOLDER_FLAG and the KF_ flags to not get defined.
+   // So we must define NTDDI_VERSION to get those flags on mingw.
+   // The docs say though here:
+   // https://msdn.microsoft.com/en-nz/library/windows/desktop/aa383745(v=vs.85).aspx
+   // that "If you define NTDDI_VERSION, you must also define _WIN32_WINNT."
+   // So we declare we require Vista or greater.
+#  ifdef __MINGW32__
+
+#    ifndef NTDDI_VERSION
+#      define NTDDI_VERSION 0x06000000
+#      define _WIN32_WINNT _WIN32_WINNT_VISTA
+#    elif NTDDI_VERSION < 0x06000000
+#      warning "If this fails to compile NTDDI_VERSION may be to low. See comments above."
+#    endif
+     // But once we define the values above we then get this linker error:
+     // "tz.cpp:(.rdata$.refptr.FOLDERID_Downloads[.refptr.FOLDERID_Downloads]+0x0): "
+     //     "undefined reference to `FOLDERID_Downloads'"
+     // which #include <initguid.h> cures see:
+     // https://support.microsoft.com/en-us/kb/130869
+#    include <initguid.h>
+     // But with <initguid.h> included, the error moves on to:
+     // error: 'FOLDERID_Downloads' was not declared in this scope
+     // Which #include <knownfolders.h> cures.
+#    include <knownfolders.h>
+
+#  endif  // __MINGW32__
+
+#  include <windows.h>
+#endif  // _WIN32
+
+#include "date/tz_private.h"
+
+#ifdef __APPLE__
+#  include "date/ios.h"
+#else
+#  define TARGET_OS_IPHONE 0
+#endif
+
+#if USE_OS_TZDB
+#  include <dirent.h>
+#endif
+#include <algorithm>
+#include <cctype>
+#include <cstdlib>
+#include <cstring>
+#include <cwchar>
+#include <exception>
+#include <fstream>
+#include <iostream>
+#include <iterator>
+#include <memory>
+#if USE_OS_TZDB
+#  include <queue>
+#endif
+#include <sstream>
+#include <string>
+#include <tuple>
+#include <vector>
+#include <sys/stat.h>
+
+// unistd.h is used on some platforms as part of the the means to get
+// the current time zone. On Win32 windows.h provides a means to do it.
+// gcc/mingw supports unistd.h on Win32 but MSVC does not.
+
+#ifdef _WIN32
+#  ifdef WINAPI_FAMILY
+#    include <winapifamily.h>
+#    if WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP
+#      define WINRT
+#      define INSTALL .
+#    endif
+#  endif
+
+#  include <io.h> // _unlink etc.
+
+#  if defined(__clang__)
+    struct IUnknown;    // fix for issue with static_cast<> in objbase.h
+                        //   (see https://github.com/philsquared/Catch/issues/690)
+#  endif
+
+#  include <shlobj.h> // CoTaskFree, ShGetKnownFolderPath etc.
+#  if HAS_REMOTE_API
+#    include <direct.h> // _mkdir
+#    include <shellapi.h> // ShFileOperation etc.
+#  endif  // HAS_REMOTE_API
+#else   // !_WIN32
+#  include <unistd.h>
+#  if !USE_OS_TZDB
+#    include <wordexp.h>
+#  endif
+#  include <limits.h>
+#  include <string.h>
+#  if !USE_SHELL_API
+#    include <sys/stat.h>
+#    include <sys/fcntl.h>
+#    include <dirent.h>
+#    include <cstring>
+#    include <sys/wait.h>
+#    include <sys/types.h>
+#  endif //!USE_SHELL_API
+#endif  // !_WIN32
+
+
+#if HAS_REMOTE_API
+   // Note curl includes windows.h so we must include curl AFTER definitions of things
+   // that affect windows.h such as NOMINMAX.
+#if defined(_MSC_VER) && defined(SHORTENED_CURL_INCLUDE)
+   // For rmt_curl nuget package
+#  include <curl.h>
+#else
+#  include <curl/curl.h>
+#endif
+#endif
+
+#ifdef _WIN32
+static CONSTDATA char folder_delimiter = '\\';
+#else   // !_WIN32
+static CONSTDATA char folder_delimiter = '/';
+#endif  // !_WIN32
+
+#if defined(__GNUC__) && __GNUC__ < 5
+   // GCC 4.9 Bug 61489 Wrong warning with -Wmissing-field-initializers
+#  pragma GCC diagnostic push
+#  pragma GCC diagnostic ignored "-Wmissing-field-initializers"
+#endif  // defined(__GNUC__) && __GNUC__ < 5
+
+#if !USE_OS_TZDB
+
+#  ifdef _WIN32
+#    ifndef WINRT
+
+namespace
+{
+    struct task_mem_deleter
+    {
+        void operator()(wchar_t buf[])
+        {
+            if (buf != nullptr)
+                CoTaskMemFree(buf);
+        }
+    };
+    using co_task_mem_ptr = std::unique_ptr<wchar_t[], task_mem_deleter>;
+}
+
+// We might need to know certain locations even if not using the remote API,
+// so keep these routines out of that block for now.
+static
+std::string
+get_known_folder(const GUID& folderid)
+{
+    std::string folder;
+    PWSTR pfolder = nullptr;
+    HRESULT hr = SHGetKnownFolderPath(folderid, KF_FLAG_DEFAULT, nullptr, &pfolder);
+    if (SUCCEEDED(hr))
+    {
+        co_task_mem_ptr folder_ptr(pfolder);
+        const wchar_t* fptr = folder_ptr.get();
+        auto state = std::mbstate_t();
+        const auto required = std::wcsrtombs(nullptr, &fptr, 0, &state);
+        if (required != 0 && required != std::size_t(-1))
+        {
+            folder.resize(required);
+            std::wcsrtombs(&folder[0], &fptr, folder.size(), &state);
+        }
+    }
+    return folder;
+}
+
+#      ifndef INSTALL
+
+// Usually something like "c:\Users\username\Downloads".
+static
+std::string
+get_download_folder()
+{
+    return get_known_folder(FOLDERID_Downloads);
+}
+
+#      endif  // !INSTALL
+
+#    endif // WINRT
+#  else // !_WIN32
+
+#    if !defined(INSTALL)
+
+static
+std::string
+expand_path(std::string path)
+{
+#      if TARGET_OS_IPHONE
+    return date::iOSUtils::get_tzdata_path();
+#      else  // !TARGET_OS_IPHONE
+    ::wordexp_t w{};
+    std::unique_ptr<::wordexp_t, void(*)(::wordexp_t*)> hold{&w, ::wordfree};
+    ::wordexp(path.c_str(), &w, 0);
+    if (w.we_wordc != 1)
+        throw std::runtime_error("Cannot expand path: " + path);
+    path = w.we_wordv[0];
+    return path;
+#      endif  // !TARGET_OS_IPHONE
+}
+
+static
+std::string
+get_download_folder()
+{
+    return expand_path("~/Downloads");
+}
+
+#    endif // !defined(INSTALL)
+
+#  endif  // !_WIN32
+
+#endif  // !USE_OS_TZDB
+
+namespace date
+{
+// +---------------------+
+// | Begin Configuration |
+// +---------------------+
+
+using namespace detail;
+
+#if !USE_OS_TZDB
+
+static
+std::string&
+access_install()
+{
+    static std::string install
+#ifndef INSTALL
+
+    = get_download_folder() + folder_delimiter + "tzdata";
+
+#else   // !INSTALL
+
+#  define STRINGIZEIMP(x) #x
+#  define STRINGIZE(x) STRINGIZEIMP(x)
+
+    = STRINGIZE(INSTALL) + std::string(1, folder_delimiter) + "tzdata";
+
+    #undef STRINGIZEIMP
+    #undef STRINGIZE
+#endif  // !INSTALL
+
+    return install;
+}
+
+void
+set_install(const std::string& s)
+{
+    access_install() = s;
+}
+
+static
+const std::string&
+get_install()
+{
+    static const std::string& ref = access_install();
+    return ref;
+}
+
+#if HAS_REMOTE_API
+static
+std::string
+get_download_gz_file(const std::string& version)
+{
+    auto file = get_install() + version + ".tar.gz";
+    return file;
+}
+#endif  // HAS_REMOTE_API
+
+#endif  // !USE_OS_TZDB
+
+// These can be used to reduce the range of the database to save memory
+CONSTDATA auto min_year = date::year::min();
+CONSTDATA auto max_year = date::year::max();
+
+CONSTDATA auto min_day = date::January/1;
+CONSTDATA auto max_day = date::December/31;
+
+#if USE_OS_TZDB
+
+CONSTCD14 const sys_seconds min_seconds = sys_days(min_year/min_day);
+
+#endif  // USE_OS_TZDB
+
+#ifndef _WIN32
+
+static
+std::string
+discover_tz_dir()
+{
+    struct stat sb;
+    using namespace std;
+#  ifndef __APPLE__
+    CONSTDATA auto tz_dir_default = "/usr/share/zoneinfo";
+    CONSTDATA auto tz_dir_buildroot = "/usr/share/zoneinfo/uclibc";
+
+    // Check special path which is valid for buildroot with uclibc builds
+    if(stat(tz_dir_buildroot, &sb) == 0 && S_ISDIR(sb.st_mode))
+        return tz_dir_buildroot;
+    else if(stat(tz_dir_default, &sb) == 0 && S_ISDIR(sb.st_mode))
+        return tz_dir_default;
+    else
+        throw runtime_error("discover_tz_dir failed to find zoneinfo\n");
+#  else  // __APPLE__
+#      if TARGET_OS_IPHONE
+    return "/var/db/timezone/zoneinfo";
+#      else
+    CONSTDATA auto timezone = "/etc/localtime";
+    if (!(lstat(timezone, &sb) == 0 && S_ISLNK(sb.st_mode) && sb.st_size > 0))
+        throw runtime_error("discover_tz_dir failed\n");
+    string result;
+    char rp[PATH_MAX+1] = {};
+    if (readlink(timezone, rp, sizeof(rp)-1) > 0)
+        result = string(rp);
+    else
+        throw system_error(errno, system_category(), "readlink() failed");
+    auto i = result.find("zoneinfo");
+    if (i == string::npos)
+        throw runtime_error("discover_tz_dir failed to find zoneinfo\n");
+    i = result.find('/', i);
+    if (i == string::npos)
+        throw runtime_error("discover_tz_dir failed to find '/'\n");
+    return result.substr(0, i);
+#      endif
+#  endif  // __APPLE__
+}
+
+static
+const std::string&
+get_tz_dir()
+{
+    static const std::string tz_dir = discover_tz_dir();
+    return tz_dir;
+}
+
+#endif
+
+// +-------------------+
+// | End Configuration |
+// +-------------------+
+
+#ifndef _MSC_VER
+static_assert(min_year <= max_year, "Configuration error");
+#endif
+
+static std::unique_ptr<tzdb> init_tzdb();
+
+tzdb_list::~tzdb_list()
+{
+    const tzdb* ptr = head_;
+    head_ = nullptr;
+    while (ptr != nullptr)
+    {
+        auto next = ptr->next;
+        delete ptr;
+        ptr = next;
+    }
+}
+
+tzdb_list::tzdb_list(tzdb_list&& x) noexcept
+   : head_{x.head_.exchange(nullptr)}
+{
+}
+
+void
+tzdb_list::push_front(tzdb* tzdb) noexcept
+{
+    tzdb->next = head_;
+    head_ = tzdb;
+}
+
+tzdb_list::const_iterator
+tzdb_list::erase_after(const_iterator p) noexcept
+{
+    auto t = p.p_->next;
+    p.p_->next = p.p_->next->next;
+    delete t;
+    return ++p;
+}
+
+struct tzdb_list::undocumented_helper
+{
+    static void push_front(tzdb_list& db_list, tzdb* tzdb) noexcept
+    {
+        db_list.push_front(tzdb);
+    }
+};
+
+static
+tzdb_list
+create_tzdb()
+{
+    tzdb_list tz_db;
+    tzdb_list::undocumented_helper::push_front(tz_db, init_tzdb().release());
+    return tz_db;
+}
+
+tzdb_list&
+get_tzdb_list()
+{
+    static tzdb_list tz_db = create_tzdb();
+    return tz_db;
+}
+
+#if !USE_OS_TZDB
+
+#ifdef _WIN32
+
+static
+void
+sort_zone_mappings(std::vector<date::detail::timezone_mapping>& mappings)
+{
+    std::sort(mappings.begin(), mappings.end(),
+        [](const date::detail::timezone_mapping& lhs,
+           const date::detail::timezone_mapping& rhs)->bool
+    {
+        auto other_result = lhs.other.compare(rhs.other);
+        if (other_result < 0)
+            return true;
+        else if (other_result == 0)
+        {
+            auto territory_result = lhs.territory.compare(rhs.territory);
+            if (territory_result < 0)
+                return true;
+            else if (territory_result == 0)
+            {
+                if (lhs.type < rhs.type)
+                    return true;
+            }
+        }
+        return false;
+    });
+}
+
+static
+bool
+native_to_standard_timezone_name(const std::string& native_tz_name,
+                                 std::string& standard_tz_name)
+{
+    // TOOD! Need be a case insensitive compare?
+    if (native_tz_name == "UTC")
+    {
+        standard_tz_name = "Etc/UTC";
+        return true;
+    }
+    standard_tz_name.clear();
+    // TODO! we can improve on linear search.
+    const auto& mappings = date::get_tzdb().mappings;
+    for (const auto& tzm : mappings)
+    {
+        if (tzm.other == native_tz_name)
+        {
+            standard_tz_name = tzm.type;
+            return true;
+        }
+    }
+    return false;
+}
+
+// Parse this XML file:
+// https://raw.githubusercontent.com/unicode-org/cldr/master/common/supplemental/windowsZones.xml
+// The parsing method is designed to be simple and quick. It is not overly
+// forgiving of change but it should diagnose basic format issues.
+// See timezone_mapping structure for more info.
+static
+std::vector<detail::timezone_mapping>
+load_timezone_mappings_from_xml_file(const std::string& input_path)
+{
+    std::size_t line_num = 0;
+    std::vector<detail::timezone_mapping> mappings;
+    std::string line;
+
+    std::ifstream is(input_path);
+    if (!is.is_open())
+    {
+        // We don't emit file exceptions because that's an implementation detail.
+        std::string msg = "Error opening time zone mapping file \"";
+        msg += input_path;
+        msg += "\".";
+        throw std::runtime_error(msg);
+    }
+
+    auto error = [&input_path, &line_num](const char* info)
+    {
+        std::string msg = "Error loading time zone mapping file \"";
+        msg += input_path;
+        msg += "\" at line ";
+        msg += std::to_string(line_num);
+        msg += ": ";
+        msg += info;
+        throw std::runtime_error(msg);
+    };
+    // [optional space]a="b"
+    auto read_attribute = [&line, &error]
+                          (const char* name, std::string& value, std::size_t startPos)
+                          ->std::size_t
+    {
+        value.clear();
+        // Skip leading space before attribute name.
+        std::size_t spos = line.find_first_not_of(' ', startPos);
+        if (spos == std::string::npos)
+            spos = startPos;
+        // Assume everything up to next = is the attribute name
+        // and that an = will always delimit that.
+        std::size_t epos = line.find('=', spos);
+        if (epos == std::string::npos)
+            error("Expected \'=\' right after attribute name.");
+        std::size_t name_len = epos - spos;
+        // Expect the name we find matches the name we expect.
+        if (line.compare(spos, name_len, name) != 0)
+        {
+            std::string msg;
+            msg = "Expected attribute name \'";
+            msg += name;
+            msg += "\' around position ";
+            msg += std::to_string(spos);
+            msg += " but found something else.";
+            error(msg.c_str());
+        }
+        ++epos; // Skip the '=' that is after the attribute name.
+        spos = epos;
+        if (spos < line.length() && line[spos] == '\"')
+            ++spos; // Skip the quote that is before the attribute value.
+        else
+        {
+            std::string msg = "Expected '\"' to begin value of attribute \'";
+            msg += name;
+            msg += "\'.";
+            error(msg.c_str());
+        }
+        epos = line.find('\"', spos);
+        if (epos == std::string::npos)
+        {
+            std::string msg = "Expected '\"' to end value of attribute \'";
+            msg += name;
+            msg += "\'.";
+            error(msg.c_str());
+        }
+        // Extract everything in between the quotes. Note no escaping is done.
+        std::size_t value_len = epos - spos;
+        value.assign(line, spos, value_len);
+        ++epos; // Skip the quote that is after the attribute value;
+        return epos;
+    };
+
+    // Quick but not overly forgiving XML mapping file processing.
+    bool mapTimezonesOpenTagFound = false;
+    bool mapTimezonesCloseTagFound = false;
+    std::size_t mapZonePos = std::string::npos;
+    std::size_t mapTimezonesPos = std::string::npos;
+    CONSTDATA char mapTimeZonesOpeningTag[] = { "<mapTimezones " };
+    CONSTDATA char mapZoneOpeningTag[] = { "<mapZone " };
+    CONSTDATA std::size_t mapZoneOpeningTagLen = sizeof(mapZoneOpeningTag) /
+                                                 sizeof(mapZoneOpeningTag[0]) - 1;
+    while (!mapTimezonesOpenTagFound)
+    {
+        std::getline(is, line);
+        ++line_num;
+        if (is.eof())
+        {
+            // If there is no mapTimezones tag is it an error?
+            // Perhaps if there are no mapZone mappings it might be ok for
+            // its parent mapTimezones element to be missing?
+            // We treat this as an error though on the assumption that if there
+            // really are no mappings we should still get a mapTimezones parent
+            // element but no mapZone elements inside. Assuming we must
+            // find something will hopefully at least catch more drastic formatting
+            // changes or errors than if we don't do this and assume nothing found.
+            error("Expected a mapTimezones opening tag.");
+        }
+        mapTimezonesPos = line.find(mapTimeZonesOpeningTag);
+        mapTimezonesOpenTagFound = (mapTimezonesPos != std::string::npos);
+    }
+
+    // NOTE: We could extract the version info that follows the opening
+    // mapTimezones tag and compare that to the version of other data we have.
+    // I would have expected them to be kept in synch but testing has shown
+    // it typically does not match anyway. So what's the point?
+    while (!mapTimezonesCloseTagFound)
+    {
+        std::ws(is);
+        std::getline(is, line);
+        ++line_num;
+        if (is.eof())
+            error("Expected a mapTimezones closing tag.");
+        if (line.empty())
+            continue;
+        mapZonePos = line.find(mapZoneOpeningTag);
+        if (mapZonePos != std::string::npos)
+        {
+            mapZonePos += mapZoneOpeningTagLen;
+            detail::timezone_mapping zm{};
+            std::size_t pos = read_attribute("other", zm.other, mapZonePos);
+            pos = read_attribute("territory", zm.territory, pos);
+            read_attribute("type", zm.type, pos);
+            mappings.push_back(std::move(zm));
+
+            continue;
+        }
+        mapTimezonesPos = line.find("</mapTimezones>");
+        mapTimezonesCloseTagFound = (mapTimezonesPos != std::string::npos);
+        if (!mapTimezonesCloseTagFound)
+        {
+            std::size_t commentPos = line.find("<!--");
+            if (commentPos == std::string::npos)
+                error("Unexpected mapping record found. A xml mapZone or comment "
+                      "attribute or mapTimezones closing tag was expected.");
+        }
+    }
+
+    is.close();
+    return mappings;
+}
+
+#endif  // _WIN32
+
+// Parsing helpers
+
+static
+std::string
+parse3(std::istream& in)
+{
+    std::string r(3, ' ');
+    ws(in);
+    r[0] = static_cast<char>(in.get());
+    r[1] = static_cast<char>(in.get());
+    r[2] = static_cast<char>(in.get());
+    return r;
+}
+
+static
+unsigned
+parse_dow(std::istream& in)
+{
+    CONSTDATA char*const dow_names[] =
+        {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
+    auto s = parse3(in);
+    auto dow = std::find(std::begin(dow_names), std::end(dow_names), s) - dow_names;
+    if (dow >= std::end(dow_names) - std::begin(dow_names))
+        throw std::runtime_error("oops: bad dow name: " + s);
+    return static_cast<unsigned>(dow);
+}
+
+static
+unsigned
+parse_month(std::istream& in)
+{
+    CONSTDATA char*const month_names[] =
+        {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+         "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
+    auto s = parse3(in);
+    auto m = std::find(std::begin(month_names), std::end(month_names), s) - month_names;
+    if (m >= std::end(month_names) - std::begin(month_names))
+        throw std::runtime_error("oops: bad month name: " + s);
+    return static_cast<unsigned>(++m);
+}
+
+static
+std::chrono::seconds
+parse_unsigned_time(std::istream& in)
+{
+    using namespace std::chrono;
+    int x;
+    in >> x;
+    auto r = seconds{hours{x}};
+    if (!in.eof() && in.peek() == ':')
+    {
+        in.get();
+        in >> x;
+        r += minutes{x};
+        if (!in.eof() && in.peek() == ':')
+        {
+            in.get();
+            in >> x;
+            r += seconds{x};
+        }
+    }
+    return r;
+}
+
+static
+std::chrono::seconds
+parse_signed_time(std::istream& in)
+{
+    ws(in);
+    auto sign = 1;
+    if (in.peek() == '-')
+    {
+        sign = -1;
+        in.get();
+    }
+    else if (in.peek() == '+')
+        in.get();
+    return sign * parse_unsigned_time(in);
+}
+
+// MonthDayTime
+
+detail::MonthDayTime::MonthDayTime(local_seconds tp, tz timezone)
+    : zone_(timezone)
+{
+    using namespace date;
+    const auto dp = date::floor<days>(tp);
+    const auto hms = make_time(tp - dp);
+    const auto ymd = year_month_day(dp);
+    u = ymd.month() / ymd.day();
+    h_ = hms.hours();
+    m_ = hms.minutes();
+    s_ = hms.seconds();
+}
+
+detail::MonthDayTime::MonthDayTime(const date::month_day& md, tz timezone)
+    : zone_(timezone)
+{
+    u = md;
+}
+
+date::day
+detail::MonthDayTime::day() const
+{
+    switch (type_)
+    {
+    case month_day:
+        return u.month_day_.day();
+    case month_last_dow:
+        return date::day{31};
+    case lteq:
+    case gteq:
+        break;
+    }
+    return u.month_day_weekday_.month_day_.day();
+}
+
+date::month
+detail::MonthDayTime::month() const
+{
+    switch (type_)
+    {
+    case month_day:
+        return u.month_day_.month();
+    case month_last_dow:
+        return u.month_weekday_last_.month();
+    case lteq:
+    case gteq:
+        break;
+    }
+    return u.month_day_weekday_.month_day_.month();
+}
+
+int
+detail::MonthDayTime::compare(date::year y, const MonthDayTime& x, date::year yx,
+                      std::chrono::seconds offset, std::chrono::minutes prev_save) const
+{
+    if (zone_ != x.zone_)
+    {
+        auto dp0 = to_sys_days(y);
+        auto dp1 = x.to_sys_days(yx);
+        if (std::abs((dp0-dp1).count()) > 1)
+            return dp0 < dp1 ? -1 : 1;
+        if (zone_ == tz::local)
+        {
+            auto tp0 = to_time_point(y) - prev_save;
+            if (x.zone_ == tz::utc)
+                tp0 -= offset;
+            auto tp1 = x.to_time_point(yx);
+            return tp0 < tp1 ? -1 : tp0 == tp1 ? 0 : 1;
+        }
+        else if (zone_ == tz::standard)
+        {
+            auto tp0 = to_time_point(y);
+            auto tp1 = x.to_time_point(yx);
+            if (x.zone_ == tz::local)
+                tp1 -= prev_save;
+            else
+                tp0 -= offset;
+            return tp0 < tp1 ? -1 : tp0 == tp1 ? 0 : 1;
+        }
+        // zone_ == tz::utc
+        auto tp0 = to_time_point(y);
+        auto tp1 = x.to_time_point(yx);
+        if (x.zone_ == tz::local)
+            tp1 -= offset + prev_save;
+        else
+            tp1 -= offset;
+        return tp0 < tp1 ? -1 : tp0 == tp1 ? 0 : 1;
+    }
+    auto const t0 = to_time_point(y);
+    auto const t1 = x.to_time_point(yx);
+    return t0 < t1 ? -1 : t0 == t1 ? 0 : 1;
+}
+
+sys_seconds
+detail::MonthDayTime::to_sys(date::year y, std::chrono::seconds offset,
+                     std::chrono::seconds save) const
+{
+    using namespace date;
+    using namespace std::chrono;
+    auto until_utc = to_time_point(y);
+    if (zone_ == tz::standard)
+        until_utc -= offset;
+    else if (zone_ == tz::local)
+        until_utc -= offset + save;
+    return until_utc;
+}
+
+detail::MonthDayTime::U&
+detail::MonthDayTime::U::operator=(const date::month_day& x)
+{
+    month_day_ = x;
+    return *this;
+}
+
+detail::MonthDayTime::U&
+detail::MonthDayTime::U::operator=(const date::month_weekday_last& x)
+{
+    month_weekday_last_ = x;
+    return *this;
+}
+
+detail::MonthDayTime::U&
+detail::MonthDayTime::U::operator=(const pair& x)
+{
+    month_day_weekday_ = x;
+    return *this;
+}
+
+date::sys_days
+detail::MonthDayTime::to_sys_days(date::year y) const
+{
+    using namespace std::chrono;
+    using namespace date;
+    switch (type_)
+    {
+    case month_day:
+        return sys_days(y/u.month_day_);
+    case month_last_dow:
+        return sys_days(y/u.month_weekday_last_);
+    case lteq:
+        {
+            auto const x = y/u.month_day_weekday_.month_day_;
+            auto const wd1 = weekday(static_cast<sys_days>(x));
+            auto const wd0 = u.month_day_weekday_.weekday_;
+            return sys_days(x) - (wd1-wd0);
+        }
+    case gteq:
+        break;
+    }
+    auto const x = y/u.month_day_weekday_.month_day_;
+    auto const wd1 = u.month_day_weekday_.weekday_;
+    auto const wd0 = weekday(static_cast<sys_days>(x));
+    return sys_days(x) + (wd1-wd0);
+}
+
+sys_seconds
+detail::MonthDayTime::to_time_point(date::year y) const
+{
+    // Add seconds first to promote to largest rep early to prevent overflow
+    return to_sys_days(y) + s_ + h_ + m_;
+}
+
+void
+detail::MonthDayTime::canonicalize(date::year y)
+{
+    using namespace std::chrono;
+    using namespace date;
+    switch (type_)
+    {
+    case month_day:
+        return;
+    case month_last_dow:
+        {
+            auto const ymd = year_month_day(sys_days(y/u.month_weekday_last_));
+            u.month_day_ = ymd.month()/ymd.day();
+            type_ = month_day;
+            return;
+        }
+    case lteq:
+        {
+            auto const x = y/u.month_day_weekday_.month_day_;
+            auto const wd1 = weekday(static_cast<sys_days>(x));
+            auto const wd0 = u.month_day_weekday_.weekday_;
+            auto const ymd = year_month_day(sys_days(x) - (wd1-wd0));
+            u.month_day_ = ymd.month()/ymd.day();
+            type_ = month_day;
+            return;
+        }
+    case gteq:
+        {
+            auto const x = y/u.month_day_weekday_.month_day_;
+            auto const wd1 = u.month_day_weekday_.weekday_;
+            auto const wd0 = weekday(static_cast<sys_days>(x));
+            auto const ymd = year_month_day(sys_days(x) + (wd1-wd0));
+            u.month_day_ = ymd.month()/ymd.day();
+            type_ = month_day;
+            return;
+        }
+    }
+}
+
+std::istream&
+detail::operator>>(std::istream& is, MonthDayTime& x)
+{
+    using namespace date;
+    using namespace std::chrono;
+    assert(((std::ios::failbit | std::ios::badbit) & is.exceptions()) ==
+            (std::ios::failbit | std::ios::badbit));
+    x = MonthDayTime{};
+    if (!is.eof() && ws(is) && !is.eof() && is.peek() != '#')
+    {
+        auto m = parse_month(is);
+        if (!is.eof() && ws(is) && !is.eof() && is.peek() != '#')
+        {
+            if (is.peek() == 'l')
+            {
+                for (int i = 0; i < 4; ++i)
+                    is.get();
+                auto dow = parse_dow(is);
+                x.type_ = MonthDayTime::month_last_dow;
+                x.u = date::month(m)/weekday(dow)[last];
+            }
+            else if (std::isalpha(is.peek()))
+            {
+                auto dow = parse_dow(is);
+                char c{};
+                is >> c;
+                if (c == '<' || c == '>')
+                {
+                    char c2{};
+                    is >> c2;
+                    if (c2 != '=')
+                        throw std::runtime_error(std::string("bad operator: ") + c + c2);
+                    int d;
+                    is >> d;
+                    if (d < 1 || d > 31)
+                        throw std::runtime_error(std::string("bad operator: ") + c + c2
+                                 + std::to_string(d));
+                    x.type_ = c == '<' ? MonthDayTime::lteq : MonthDayTime::gteq;
+                    x.u = MonthDayTime::pair{ date::month(m) / d, date::weekday(dow) };
+                }
+                else
+                    throw std::runtime_error(std::string("bad operator: ") + c);
+            }
+            else  // if (std::isdigit(is.peek())
+            {
+                int d;
+                is >> d;
+                if (d < 1 || d > 31)
+                    throw std::runtime_error(std::string("day of month: ")
+                             + std::to_string(d));
+                x.type_ = MonthDayTime::month_day;
+                x.u = date::month(m)/d;
+            }
+            if (!is.eof() && ws(is) && !is.eof() && is.peek() != '#')
+            {
+                int t;
+                is >> t;
+                x.h_ = hours{t};
+                if (!is.eof() && is.peek() == ':')
+                {
+                    is.get();
+                    is >> t;
+                    x.m_ = minutes{t};
+                    if (!is.eof() && is.peek() == ':')
+                    {
+                        is.get();
+                        is >> t;
+                        x.s_ = seconds{t};
+                    }
+                }
+                if (!is.eof() && std::isalpha(is.peek()))
+                {
+                    char c;
+                    is >> c;
+                    switch (c)
+                    {
+                    case 's':
+                        x.zone_ = tz::standard;
+                        break;
+                    case 'u':
+                        x.zone_ = tz::utc;
+                        break;
+                    }
+                }
+            }
+        }
+        else
+        {
+            x.u = month{m}/1;
+        }
+    }
+    return is;
+}
+
+std::ostream&
+detail::operator<<(std::ostream& os, const MonthDayTime& x)
+{
+    switch (x.type_)
+    {
+    case MonthDayTime::month_day:
+        os << x.u.month_day_ << "                  ";
+        break;
+    case MonthDayTime::month_last_dow:
+        os << x.u.month_weekday_last_ << "           ";
+        break;
+    case MonthDayTime::lteq:
+        os << x.u.month_day_weekday_.weekday_ << " on or before "
+           << x.u.month_day_weekday_.month_day_ << "  ";
+        break;
+    case MonthDayTime::gteq:
+        if ((static_cast<unsigned>(x.day()) - 1) % 7 == 0)
+        {
+            os << (x.u.month_day_weekday_.month_day_.month() /
+                   x.u.month_day_weekday_.weekday_[
+                       (static_cast<unsigned>(x.day()) - 1)/7+1]) << "              ";
+        }
+        else
+        {
+            os << x.u.month_day_weekday_.weekday_ << " on or after "
+               << x.u.month_day_weekday_.month_day_ << "  ";
+        }
+        break;
+    }
+    os << date::make_time(x.s_ + x.h_ + x.m_);
+    if (x.zone_ == tz::utc)
+        os << "UTC   ";
+    else if (x.zone_ == tz::standard)
+        os << "STD   ";
+    else
+        os << "      ";
+    return os;
+}
+
+// Rule
+
+detail::Rule::Rule(const std::string& s)
+{
+    try
+    {
+        using namespace date;
+        using namespace std::chrono;
+        std::istringstream in(s);
+        in.exceptions(std::ios::failbit | std::ios::badbit);
+        std::string word;
+        in >> word >> name_;
+        int x;
+        ws(in);
+        if (std::isalpha(in.peek()))
+        {
+            in >> word;
+            if (word == "min")
+            {
+                starting_year_ = year::min();
+            }
+            else
+                throw std::runtime_error("Didn't find expected word: " + word);
+        }
+        else
+        {
+            in >> x;
+            starting_year_ = year{x};
+        }
+        std::ws(in);
+        if (std::isalpha(in.peek()))
+        {
+            in >> word;
+            if (word == "only")
+            {
+                ending_year_ = starting_year_;
+            }
+            else if (word == "max")
+            {
+                ending_year_ = year::max();
+            }
+            else
+                throw std::runtime_error("Didn't find expected word: " + word);
+        }
+        else
+        {
+            in >> x;
+            ending_year_ = year{x};
+        }
+        in >> word;  // TYPE (always "-")
+        assert(word == "-");
+        in >> starting_at_;
+        save_ = duration_cast<minutes>(parse_signed_time(in));
+        in >> abbrev_;
+        if (abbrev_ == "-")
+            abbrev_.clear();
+        assert(hours{-1} <= save_ && save_ <= hours{2});
+    }
+    catch (...)
+    {
+        std::cerr << s << '\n';
+        std::cerr << *this << '\n';
+        throw;
+    }
+}
+
+detail::Rule::Rule(const Rule& r, date::year starting_year, date::year ending_year)
+    : name_(r.name_)
+    , starting_year_(starting_year)
+    , ending_year_(ending_year)
+    , starting_at_(r.starting_at_)
+    , save_(r.save_)
+    , abbrev_(r.abbrev_)
+{
+}
+
+bool
+detail::operator==(const Rule& x, const Rule& y)
+{
+    if (std::tie(x.name_, x.save_, x.starting_year_, x.ending_year_) ==
+        std::tie(y.name_, y.save_, y.starting_year_, y.ending_year_))
+        return x.month() == y.month() && x.day() == y.day();
+    return false;
+}
+
+bool
+detail::operator<(const Rule& x, const Rule& y)
+{
+    using namespace std::chrono;
+    auto const xm = x.month();
+    auto const ym = y.month();
+    if (std::tie(x.name_, x.starting_year_, xm, x.ending_year_) <
+        std::tie(y.name_, y.starting_year_, ym, y.ending_year_))
+        return true;
+    if (std::tie(x.name_, x.starting_year_, xm, x.ending_year_) >
+        std::tie(y.name_, y.starting_year_, ym, y.ending_year_))
+        return false;
+    return x.day() < y.day();
+}
+
+bool
+detail::operator==(const Rule& x, const date::year& y)
+{
+    return x.starting_year_ <= y && y <= x.ending_year_;
+}
+
+bool
+detail::operator<(const Rule& x, const date::year& y)
+{
+    return x.ending_year_ < y;
+}
+
+bool
+detail::operator==(const date::year& x, const Rule& y)
+{
+    return y.starting_year_ <= x && x <= y.ending_year_;
+}
+
+bool
+detail::operator<(const date::year& x, const Rule& y)
+{
+    return x < y.starting_year_;
+}
+
+bool
+detail::operator==(const Rule& x, const std::string& y)
+{
+    return x.name() == y;
+}
+
+bool
+detail::operator<(const Rule& x, const std::string& y)
+{
+    return x.name() < y;
+}
+
+bool
+detail::operator==(const std::string& x, const Rule& y)
+{
+    return y.name() == x;
+}
+
+bool
+detail::operator<(const std::string& x, const Rule& y)
+{
+    return x < y.name();
+}
+
+std::ostream&
+detail::operator<<(std::ostream& os, const Rule& r)
+{
+    using namespace date;
+    using namespace std::chrono;
+    detail::save_ostream<char> _(os);
+    os.fill(' ');
+    os.flags(std::ios::dec | std::ios::left);
+    os.width(15);
+    os << r.name_;
+    os << r.starting_year_ << "    " << r.ending_year_ << "    ";
+    os << r.starting_at_;
+    if (r.save_ >= minutes{0})
+        os << ' ';
+    os << date::make_time(r.save_) << "   ";
+    os << r.abbrev_;
+    return os;
+}
+
+date::day
+detail::Rule::day() const
+{
+    return starting_at_.day();
+}
+
+date::month
+detail::Rule::month() const
+{
+    return starting_at_.month();
+}
+
+struct find_rule_by_name
+{
+    bool operator()(const Rule& x, const std::string& nm) const
+    {
+        return x.name() < nm;
+    }
+
+    bool operator()(const std::string& nm, const Rule& x) const
+    {
+        return nm < x.name();
+    }
+};
+
+bool
+detail::Rule::overlaps(const Rule& x, const Rule& y)
+{
+    // assume x.starting_year_ <= y.starting_year_;
+    if (!(x.starting_year_ <= y.starting_year_))
+    {
+        std::cerr << x << '\n';
+        std::cerr << y << '\n';
+        assert(x.starting_year_ <= y.starting_year_);
+    }
+    if (y.starting_year_ > x.ending_year_)
+        return false;
+    return !(x.starting_year_ == y.starting_year_ && x.ending_year_ == y.ending_year_);
+}
+
+void
+detail::Rule::split(std::vector<Rule>& rules, std::size_t i, std::size_t k, std::size_t& e)
+{
+    using namespace date;
+    using difference_type = std::vector<Rule>::iterator::difference_type;
+    // rules[i].starting_year_ <= rules[k].starting_year_ &&
+    //     rules[i].ending_year_ >= rules[k].starting_year_ &&
+    //     (rules[i].starting_year_ != rules[k].starting_year_ ||
+    //      rules[i].ending_year_ != rules[k].ending_year_)
+    assert(rules[i].starting_year_ <= rules[k].starting_year_ &&
+           rules[i].ending_year_ >= rules[k].starting_year_ &&
+           (rules[i].starting_year_ != rules[k].starting_year_ ||
+            rules[i].ending_year_ != rules[k].ending_year_));
+    if (rules[i].starting_year_ == rules[k].starting_year_)
+    {
+        if (rules[k].ending_year_ < rules[i].ending_year_)
+        {
+            rules.insert(rules.begin() + static_cast<difference_type>(k+1),
+                         Rule(rules[i], rules[k].ending_year_ + years{1},
+                              std::move(rules[i].ending_year_)));
+            ++e;
+            rules[i].ending_year_ = rules[k].ending_year_;
+        }
+        else  // rules[k].ending_year_ > rules[i].ending_year_
+        {
+            rules.insert(rules.begin() + static_cast<difference_type>(k+1),
+                         Rule(rules[k], rules[i].ending_year_ + years{1},
+                              std::move(rules[k].ending_year_)));
+            ++e;
+            rules[k].ending_year_ = rules[i].ending_year_;
+        }
+    }
+    else  // rules[i].starting_year_ < rules[k].starting_year_
+    {
+        if (rules[k].ending_year_ < rules[i].ending_year_)
+        {
+            rules.insert(rules.begin() + static_cast<difference_type>(k),
+                         Rule(rules[i], rules[k].starting_year_, rules[k].ending_year_));
+            ++k;
+            rules.insert(rules.begin() + static_cast<difference_type>(k+1),
+                         Rule(rules[i], rules[k].ending_year_ + years{1},
+                              std::move(rules[i].ending_year_)));
+            rules[i].ending_year_ = rules[k].starting_year_ - years{1};
+            e += 2;
+        }
+        else if (rules[k].ending_year_ > rules[i].ending_year_)
+        {
+            rules.insert(rules.begin() + static_cast<difference_type>(k),
+                         Rule(rules[i], rules[k].starting_year_, rules[i].ending_year_));
+            ++k;
+            rules.insert(rules.begin() + static_cast<difference_type>(k+1),
+                         Rule(rules[k], rules[i].ending_year_ + years{1},
+                         std::move(rules[k].ending_year_)));
+            e += 2;
+            rules[k].ending_year_ = std::move(rules[i].ending_year_);
+            rules[i].ending_year_ = rules[k].starting_year_ - years{1};
+        }
+        else  // rules[k].ending_year_ == rules[i].ending_year_
+        {
+            rules.insert(rules.begin() + static_cast<difference_type>(k),
+                         Rule(rules[i], rules[k].starting_year_,
+                         std::move(rules[i].ending_year_)));
+            ++k;
+            ++e;
+            rules[i].ending_year_ = rules[k].starting_year_ - years{1};
+        }
+    }
+}
+
+void
+detail::Rule::split_overlaps(std::vector<Rule>& rules, std::size_t i, std::size_t& e)
+{
+    using difference_type = std::vector<Rule>::iterator::difference_type;
+    auto j = i;
+    for (; i + 1 < e; ++i)
+    {
+        for (auto k = i + 1; k < e; ++k)
+        {
+            if (overlaps(rules[i], rules[k]))
+            {
+                split(rules, i, k, e);
+                std::sort(rules.begin() + static_cast<difference_type>(i),
+                          rules.begin() + static_cast<difference_type>(e));
+            }
+        }
+    }
+    for (; j < e; ++j)
+    {
+        if (rules[j].starting_year() == rules[j].ending_year())
+            rules[j].starting_at_.canonicalize(rules[j].starting_year());
+    }
+}
+
+void
+detail::Rule::split_overlaps(std::vector<Rule>& rules)
+{
+    using difference_type = std::vector<Rule>::iterator::difference_type;
+    for (std::size_t i = 0; i < rules.size();)
+    {
+        auto e = static_cast<std::size_t>(std::upper_bound(
+            rules.cbegin()+static_cast<difference_type>(i), rules.cend(), rules[i].name(),
+            [](const std::string& nm, const Rule& x)
+            {
+                return nm < x.name();
+            }) - rules.cbegin());
+        split_overlaps(rules, i, e);
+        auto first_rule = rules.begin() + static_cast<difference_type>(i);
+        auto last_rule = rules.begin() + static_cast<difference_type>(e);
+        auto t = std::lower_bound(first_rule, last_rule, min_year);
+        if (t > first_rule+1)
+        {
+            if (t == last_rule || t->starting_year() >= min_year)
+                --t;
+            auto d = static_cast<std::size_t>(t - first_rule);
+            rules.erase(first_rule, t);
+            e -= d;
+        }
+        first_rule = rules.begin() + static_cast<difference_type>(i);
+        last_rule = rules.begin() + static_cast<difference_type>(e);
+        t = std::upper_bound(first_rule, last_rule, max_year);
+        if (t != last_rule)
+        {
+            auto d = static_cast<std::size_t>(last_rule - t);
+            rules.erase(t, last_rule);
+            e -= d;
+        }
+        i = e;
+    }
+    rules.shrink_to_fit();
+}
+
+// Find the rule that comes chronologically before Rule r.  For multi-year rules,
+// y specifies which rules in r.  For single year rules, y is assumed to be equal
+// to the year specified by r.
+// Returns a pointer to the chronologically previous rule, and the year within
+// that rule.  If there is no previous rule, returns nullptr and year::min().
+// Preconditions:
+//     r->starting_year() <= y && y <= r->ending_year()
+static
+std::pair<const Rule*, date::year>
+find_previous_rule(const Rule* r, date::year y)
+{
+    using namespace date;
+    auto const& rules = get_tzdb().rules;
+    if (y == r->starting_year())
+    {
+        if (r == &rules.front() || r->name() != r[-1].name())
+            std::terminate();  // never called with first rule
+        --r;
+        if (y == r->starting_year())
+            return {r, y};
+        return {r, r->ending_year()};
+    }
+    if (r == &rules.front() || r->name() != r[-1].name() ||
+        r[-1].starting_year() < r->starting_year())
+    {
+        while (r < &rules.back() && r->name() == r[1].name() &&
+               r->starting_year() == r[1].starting_year())
+            ++r;
+        return {r, --y};
+    }
+    --r;
+    return {r, y};
+}
+
+// Find the rule that comes chronologically after Rule r.  For multi-year rules,
+// y specifies which rules in r.  For single year rules, y is assumed to be equal
+// to the year specified by r.
+// Returns a pointer to the chronologically next rule, and the year within
+// that rule.  If there is no next rule, return a pointer to a defaulted rule
+// and y+1.
+// Preconditions:
+//     first <= r && r < last && r->starting_year() <= y && y <= r->ending_year()
+//     [first, last) all have the same name
+static
+std::pair<const Rule*, date::year>
+find_next_rule(const Rule* first_rule, const Rule* last_rule, const Rule* r, date::year y)
+{
+    using namespace date;
+    if (y == r->ending_year())
+    {
+        if (r == last_rule-1)
+            return {nullptr, year::max()};
+        ++r;
+        if (y == r->ending_year())
+            return {r, y};
+        return {r, r->starting_year()};
+    }
+    if (r == last_rule-1 || r->ending_year() < r[1].ending_year())
+    {
+        while (r > first_rule && r->starting_year() == r[-1].starting_year())
+            --r;
+        return {r, ++y};
+    }
+    ++r;
+    return {r, y};
+}
+
+// Find the rule that comes chronologically after Rule r.  For multi-year rules,
+// y specifies which rules in r.  For single year rules, y is assumed to be equal
+// to the year specified by r.
+// Returns a pointer to the chronologically next rule, and the year within
+// that rule.  If there is no next rule, return nullptr and year::max().
+// Preconditions:
+//     r->starting_year() <= y && y <= r->ending_year()
+static
+std::pair<const Rule*, date::year>
+find_next_rule(const Rule* r, date::year y)
+{
+    using namespace date;
+    auto const& rules = get_tzdb().rules;
+    if (y == r->ending_year())
+    {
+        if (r == &rules.back() || r->name() != r[1].name())
+            return {nullptr, year::max()};
+        ++r;
+        if (y == r->ending_year())
+            return {r, y};
+        return {r, r->starting_year()};
+    }
+    if (r == &rules.back() || r->name() != r[1].name() ||
+        r->ending_year() < r[1].ending_year())
+    {
+        while (r > &rules.front() && r->name() == r[-1].name() &&
+               r->starting_year() == r[-1].starting_year())
+            --r;
+        return {r, ++y};
+    }
+    ++r;
+    return {r, y};
+}
+
+static
+const Rule*
+find_first_std_rule(const std::pair<const Rule*, const Rule*>& eqr)
+{
+    auto r = eqr.first;
+    auto ry = r->starting_year();
+    while (r->save() != std::chrono::minutes{0})
+    {
+        std::tie(r, ry) = find_next_rule(eqr.first, eqr.second, r, ry);
+        if (r == nullptr)
+            throw std::runtime_error("Could not find standard offset in rule "
+                                     + eqr.first->name());
+    }
+    return r;
+}
+
+static
+std::pair<const Rule*, date::year>
+find_rule_for_zone(const std::pair<const Rule*, const Rule*>& eqr,
+                   const date::year& y, const std::chrono::seconds& offset,
+                   const MonthDayTime& mdt)
+{
+    assert(eqr.first != nullptr);
+    assert(eqr.second != nullptr);
+
+    using namespace std::chrono;
+    using namespace date;
+    auto r = eqr.first;
+    auto ry = r->starting_year();
+    auto prev_save = minutes{0};
+    auto prev_year = year::min();
+    const Rule* prev_rule = nullptr;
+    while (r != nullptr)
+    {
+        if (mdt.compare(y, r->mdt(), ry, offset, prev_save) <= 0)
+            break;
+        prev_rule = r;
+        prev_year = ry;
+        prev_save = prev_rule->save();
+        std::tie(r, ry) = find_next_rule(eqr.first, eqr.second, r, ry);
+    }
+    return {prev_rule, prev_year};
+}
+
+static
+std::pair<const Rule*, date::year>
+find_rule_for_zone(const std::pair<const Rule*, const Rule*>& eqr,
+                   const sys_seconds& tp_utc,
+                   const local_seconds& tp_std,
+                   const local_seconds& tp_loc)
+{
+    using namespace std::chrono;
+    using namespace date;
+    auto r = eqr.first;
+    auto ry = r->starting_year();
+    auto prev_save = minutes{0};
+    auto prev_year = year::min();
+    const Rule* prev_rule = nullptr;
+    while (r != nullptr)
+    {
+        bool found = false;
+        switch (r->mdt().zone())
+        {
+        case tz::utc:
+            found = tp_utc < r->mdt().to_time_point(ry);
+            break;
+        case tz::standard:
+            found = sys_seconds{tp_std.time_since_epoch()} < r->mdt().to_time_point(ry);
+            break;
+        case tz::local:
+            found = sys_seconds{tp_loc.time_since_epoch()} < r->mdt().to_time_point(ry);
+            break;
+        }
+        if (found)
+            break;
+        prev_rule = r;
+        prev_year = ry;
+        prev_save = prev_rule->save();
+        std::tie(r, ry) = find_next_rule(eqr.first, eqr.second, r, ry);
+    }
+    return {prev_rule, prev_year};
+}
+
+static
+sys_info
+find_rule(const std::pair<const Rule*, date::year>& first_rule,
+          const std::pair<const Rule*, date::year>& last_rule,
+          const date::year& y, const std::chrono::seconds& offset,
+          const MonthDayTime& mdt, const std::chrono::minutes& initial_save,
+          const std::string& initial_abbrev)
+{
+    using namespace std::chrono;
+    using namespace date;
+    auto r = first_rule.first;
+    auto ry = first_rule.second;
+    sys_info x{sys_days(year::min()/min_day), sys_days(year::max()/max_day),
+               seconds{0}, initial_save, initial_abbrev};
+    while (r != nullptr)
+    {
+        auto tr = r->mdt().to_sys(ry, offset, x.save);
+        auto tx = mdt.to_sys(y, offset, x.save);
+        // Find last rule where tx >= tr
+        if (tx <= tr || (r == last_rule.first && ry == last_rule.second))
+        {
+            if (tx < tr && r == first_rule.first && ry == first_rule.second)
+            {
+                x.end = r->mdt().to_sys(ry, offset, x.save);
+                break;
+            }
+            if (tx < tr)
+            {
+                std::tie(r, ry) = find_previous_rule(r, ry);  // can't return nullptr for r
+                assert(r != nullptr);
+            }
+            // r != nullptr && tx >= tr (if tr were to be recomputed)
+            auto prev_save = initial_save;
+            if (!(r == first_rule.first && ry == first_rule.second))
+                prev_save = find_previous_rule(r, ry).first->save();
+            x.begin = r->mdt().to_sys(ry, offset, prev_save);
+            x.save = r->save();
+            x.abbrev = r->abbrev();
+            if (!(r == last_rule.first && ry == last_rule.second))
+            {
+                std::tie(r, ry) = find_next_rule(r, ry);  // can't return nullptr for r
+                assert(r != nullptr);
+                x.end = r->mdt().to_sys(ry, offset, x.save);
+            }
+            else
+                x.end = sys_days(year::max()/max_day);
+            break;
+        }
+        x.save = r->save();
+        std::tie(r, ry) = find_next_rule(r, ry);  // Can't return nullptr for r
+        assert(r != nullptr);
+    }
+    return x;
+}
+
+// zonelet
+
+detail::zonelet::~zonelet()
+{
+#if !defined(_MSC_VER) || (_MSC_VER >= 1900)
+    using minutes = std::chrono::minutes;
+    using string = std::string;
+    if (tag_ == has_save)
+        u.save_.~minutes();
+    else
+        u.rule_.~string();
+#endif
+}
+
+detail::zonelet::zonelet()
+{
+#if !defined(_MSC_VER) || (_MSC_VER >= 1900)
+    ::new(&u.rule_) std::string();
+#endif
+}
+
+detail::zonelet::zonelet(const zonelet& i)
+    : gmtoff_(i.gmtoff_)
+    , tag_(i.tag_)
+    , format_(i.format_)
+    , until_year_(i.until_year_)
+    , until_date_(i.until_date_)
+    , until_utc_(i.until_utc_)
+    , until_std_(i.until_std_)
+    , until_loc_(i.until_loc_)
+    , initial_save_(i.initial_save_)
+    , initial_abbrev_(i.initial_abbrev_)
+    , first_rule_(i.first_rule_)
+    , last_rule_(i.last_rule_)
+{
+#if !defined(_MSC_VER) || (_MSC_VER >= 1900)
+    if (tag_ == has_save)
+        ::new(&u.save_) std::chrono::minutes(i.u.save_);
+    else
+        ::new(&u.rule_) std::string(i.u.rule_);
+#else
+    if (tag_ == has_save)
+        u.save_ = i.u.save_;
+    else
+        u.rule_ = i.u.rule_;
+#endif
+}
+
+#endif  // !USE_OS_TZDB
+
+// time_zone
+
+#if USE_OS_TZDB
+
+time_zone::time_zone(const std::string& s, detail::undocumented)
+    : name_(s)
+    , adjusted_(new std::once_flag{})
+{
+}
+
+enum class endian
+{
+    native = __BYTE_ORDER__,
+    little = __ORDER_LITTLE_ENDIAN__,
+    big    = __ORDER_BIG_ENDIAN__
+};
+
+static
+inline
+std::uint32_t
+reverse_bytes(std::uint32_t i)
+{
+    return
+        (i & 0xff000000u) >> 24 |
+        (i & 0x00ff0000u) >> 8 |
+        (i & 0x0000ff00u) << 8 |
+        (i & 0x000000ffu) << 24;
+}
+
+static
+inline
+std::uint64_t
+reverse_bytes(std::uint64_t i)
+{
+    return
+        (i & 0xff00000000000000ull) >> 56 |
+        (i & 0x00ff000000000000ull) >> 40 |
+        (i & 0x0000ff0000000000ull) >> 24 |
+        (i & 0x000000ff00000000ull) >> 8 |
+        (i & 0x00000000ff000000ull) << 8 |
+        (i & 0x0000000000ff0000ull) << 24 |
+        (i & 0x000000000000ff00ull) << 40 |
+        (i & 0x00000000000000ffull) << 56;
+}
+
+template <class T>
+static
+inline
+void
+maybe_reverse_bytes(T&, std::false_type)
+{
+}
+
+static
+inline
+void
+maybe_reverse_bytes(std::int32_t& t, std::true_type)
+{
+    t = static_cast<std::int32_t>(reverse_bytes(static_cast<std::uint32_t>(t)));
+}
+
+static
+inline
+void
+maybe_reverse_bytes(std::int64_t& t, std::true_type)
+{
+    t = static_cast<std::int64_t>(reverse_bytes(static_cast<std::uint64_t>(t)));
+}
+
+template <class T>
+static
+inline
+void
+maybe_reverse_bytes(T& t)
+{
+    maybe_reverse_bytes(t, std::integral_constant<bool,
+                                                  endian::native == endian::little>{});
+}
+
+static
+void
+load_header(std::istream& inf)
+{
+    // Read TZif
+    auto t = inf.get();
+    auto z = inf.get();
+    auto i = inf.get();
+    auto f = inf.get();
+#ifndef NDEBUG
+    assert(t == 'T');
+    assert(z == 'Z');
+    assert(i == 'i');
+    assert(f == 'f');
+#else
+    (void)t;
+    (void)z;
+    (void)i;
+    (void)f;
+#endif
+}
+
+static
+unsigned char
+load_version(std::istream& inf)
+{
+    // Read version
+    auto v = inf.get();
+    assert(v != EOF);
+    return static_cast<unsigned char>(v);
+}
+
+static
+void
+skip_reserve(std::istream& inf)
+{
+    inf.ignore(15);
+}
+
+static
+void
+load_counts(std::istream& inf,
+            std::int32_t& tzh_ttisgmtcnt, std::int32_t& tzh_ttisstdcnt,
+            std::int32_t& tzh_leapcnt,    std::int32_t& tzh_timecnt,
+            std::int32_t& tzh_typecnt,    std::int32_t& tzh_charcnt)
+{
+    // Read counts;
+    inf.read(reinterpret_cast<char*>(&tzh_ttisgmtcnt), 4);
+    maybe_reverse_bytes(tzh_ttisgmtcnt);
+    inf.read(reinterpret_cast<char*>(&tzh_ttisstdcnt), 4);
+    maybe_reverse_bytes(tzh_ttisstdcnt);
+    inf.read(reinterpret_cast<char*>(&tzh_leapcnt), 4);
+    maybe_reverse_bytes(tzh_leapcnt);
+    inf.read(reinterpret_cast<char*>(&tzh_timecnt), 4);
+    maybe_reverse_bytes(tzh_timecnt);
+    inf.read(reinterpret_cast<char*>(&tzh_typecnt), 4);
+    maybe_reverse_bytes(tzh_typecnt);
+    inf.read(reinterpret_cast<char*>(&tzh_charcnt), 4);
+    maybe_reverse_bytes(tzh_charcnt);
+}
+
+template <class TimeType>
+static
+std::vector<detail::transition>
+load_transitions(std::istream& inf, std::int32_t tzh_timecnt)
+{
+    // Read transitions
+    using namespace std::chrono;
+    std::vector<detail::transition> transitions;
+    transitions.reserve(static_cast<unsigned>(tzh_timecnt));
+    for (std::int32_t i = 0; i < tzh_timecnt; ++i)
+    {
+        TimeType t;
+        inf.read(reinterpret_cast<char*>(&t), sizeof(t));
+        maybe_reverse_bytes(t);
+        transitions.emplace_back(sys_seconds{seconds{t}});
+        if (transitions.back().timepoint < min_seconds)
+            transitions.back().timepoint = min_seconds;
+    }
+    return transitions;
+}
+
+static
+std::vector<std::uint8_t>
+load_indices(std::istream& inf, std::int32_t tzh_timecnt)
+{
+    // Read indices
+    std::vector<std::uint8_t> indices;
+    indices.reserve(static_cast<unsigned>(tzh_timecnt));
+    for (std::int32_t i = 0; i < tzh_timecnt; ++i)
+    {
+        std::uint8_t t;
+        inf.read(reinterpret_cast<char*>(&t), sizeof(t));
+        indices.emplace_back(t);
+    }
+    return indices;
+}
+
+static
+std::vector<ttinfo>
+load_ttinfo(std::istream& inf, std::int32_t tzh_typecnt)
+{
+    // Read ttinfo
+    std::vector<ttinfo> ttinfos;
+    ttinfos.reserve(static_cast<unsigned>(tzh_typecnt));
+    for (std::int32_t i = 0; i < tzh_typecnt; ++i)
+    {
+        ttinfo t;
+        inf.read(reinterpret_cast<char*>(&t), 6);
+        maybe_reverse_bytes(t.tt_gmtoff);
+        ttinfos.emplace_back(t);
+    }
+    return ttinfos;
+}
+
+static
+std::string
+load_abbreviations(std::istream& inf, std::int32_t tzh_charcnt)
+{
+    // Read abbreviations
+    std::string abbrev;
+    abbrev.resize(static_cast<unsigned>(tzh_charcnt), '\0');
+    inf.read(&abbrev[0], tzh_charcnt);
+    return abbrev;
+}
+
+#if !MISSING_LEAP_SECONDS
+
+template <class TimeType>
+static
+std::vector<leap>
+load_leaps(std::istream& inf, std::int32_t tzh_leapcnt)
+{
+    // Read tzh_leapcnt pairs
+    using namespace std::chrono;
+    std::vector<leap> leap_seconds;
+    leap_seconds.reserve(static_cast<std::size_t>(tzh_leapcnt));
+    for (std::int32_t i = 0; i < tzh_leapcnt; ++i)
+    {
+        TimeType     t0;
+        std::int32_t t1;
+        inf.read(reinterpret_cast<char*>(&t0), sizeof(t0));
+        inf.read(reinterpret_cast<char*>(&t1), sizeof(t1));
+        maybe_reverse_bytes(t0);
+        maybe_reverse_bytes(t1);
+        leap_seconds.emplace_back(sys_seconds{seconds{t0 - (t1-1)}},
+                                  detail::undocumented{});
+    }
+    return leap_seconds;
+}
+
+template <class TimeType>
+static
+std::vector<leap>
+load_leap_data(std::istream& inf,
+               std::int32_t tzh_leapcnt, std::int32_t tzh_timecnt,
+               std::int32_t tzh_typecnt, std::int32_t tzh_charcnt)
+{
+    inf.ignore(tzh_timecnt*static_cast<std::int32_t>(sizeof(TimeType)) + tzh_timecnt +
+               tzh_typecnt*6 + tzh_charcnt);
+    return load_leaps<TimeType>(inf, tzh_leapcnt);
+}
+
+static
+std::vector<leap>
+load_just_leaps(std::istream& inf)
+{
+    // Read tzh_leapcnt pairs
+    using namespace std::chrono;
+    load_header(inf);
+    auto v = load_version(inf);
+    std::int32_t tzh_ttisgmtcnt, tzh_ttisstdcnt, tzh_leapcnt,
+                 tzh_timecnt,    tzh_typecnt,    tzh_charcnt;
+    skip_reserve(inf);
+    load_counts(inf, tzh_ttisgmtcnt, tzh_ttisstdcnt, tzh_leapcnt,
+                     tzh_timecnt,    tzh_typecnt,    tzh_charcnt);
+    if (v == 0)
+        return load_leap_data<int32_t>(inf, tzh_leapcnt, tzh_timecnt, tzh_typecnt,
+                                       tzh_charcnt);
+#if !defined(NDEBUG)
+    inf.ignore((4+1)*tzh_timecnt + 6*tzh_typecnt + tzh_charcnt + 8*tzh_leapcnt +
+               tzh_ttisstdcnt + tzh_ttisgmtcnt);
+    load_header(inf);
+    auto v2 = load_version(inf);
+    assert(v == v2);
+    skip_reserve(inf);
+#else  // defined(NDEBUG)
+    inf.ignore((4+1)*tzh_timecnt + 6*tzh_typecnt + tzh_charcnt + 8*tzh_leapcnt +
+               tzh_ttisstdcnt + tzh_ttisgmtcnt + (4+1+15));
+#endif  // defined(NDEBUG)
+    load_counts(inf, tzh_ttisgmtcnt, tzh_ttisstdcnt, tzh_leapcnt,
+                     tzh_timecnt,    tzh_typecnt,    tzh_charcnt);
+    return load_leap_data<int64_t>(inf, tzh_leapcnt, tzh_timecnt, tzh_typecnt,
+                                   tzh_charcnt);
+}
+
+#endif  // !MISSING_LEAP_SECONDS
+
+template <class TimeType>
+void
+time_zone::load_data(std::istream& inf,
+                     std::int32_t tzh_leapcnt, std::int32_t tzh_timecnt,
+                     std::int32_t tzh_typecnt, std::int32_t tzh_charcnt)
+{
+    using namespace std::chrono;
+    transitions_ = load_transitions<TimeType>(inf, tzh_timecnt);
+    auto indices = load_indices(inf, tzh_timecnt);
+    auto infos = load_ttinfo(inf, tzh_typecnt);
+    auto abbrev = load_abbreviations(inf, tzh_charcnt);
+#if !MISSING_LEAP_SECONDS
+    auto& leap_seconds = get_tzdb_list().front().leaps;
+    if (leap_seconds.empty() && tzh_leapcnt > 0)
+        leap_seconds = load_leaps<TimeType>(inf, tzh_leapcnt);
+#endif
+    ttinfos_.reserve(infos.size());
+    for (auto& info : infos)
+    {
+        ttinfos_.push_back({seconds{info.tt_gmtoff},
+                            abbrev.c_str() + info.tt_abbrind,
+                            info.tt_isdst != 0});
+    }
+    auto i = 0u;
+    if (transitions_.empty() || transitions_.front().timepoint != min_seconds)
+    {
+        transitions_.emplace(transitions_.begin(), min_seconds);
+        auto tf = std::find_if(ttinfos_.begin(), ttinfos_.end(),
+                               [](const expanded_ttinfo& ti)
+                                   {return ti.is_dst == 0;});
+        if (tf == ttinfos_.end())
+            tf = ttinfos_.begin();
+        transitions_[i].info = &*tf;
+        ++i;
+    }
+    for (auto j = 0u; i < transitions_.size(); ++i, ++j)
+        transitions_[i].info = ttinfos_.data() + indices[j];
+}
+
+void
+time_zone::init_impl()
+{
+    using namespace std;
+    using namespace std::chrono;
+    auto name = get_tz_dir() + ('/' + name_);
+    std::ifstream inf(name);
+    if (!inf.is_open())
+        throw std::runtime_error{"Unable to open " + name};
+    inf.exceptions(std::ios::failbit | std::ios::badbit);
+    load_header(inf);
+    auto v = load_version(inf);
+    std::int32_t tzh_ttisgmtcnt, tzh_ttisstdcnt, tzh_leapcnt,
+                 tzh_timecnt,    tzh_typecnt,    tzh_charcnt;
+    skip_reserve(inf);
+    load_counts(inf, tzh_ttisgmtcnt, tzh_ttisstdcnt, tzh_leapcnt,
+                     tzh_timecnt,    tzh_typecnt,    tzh_charcnt);
+    if (v == 0)
+    {
+        load_data<int32_t>(inf, tzh_leapcnt, tzh_timecnt, tzh_typecnt, tzh_charcnt);
+    }
+    else
+    {
+#if !defined(NDEBUG)
+        inf.ignore((4+1)*tzh_timecnt + 6*tzh_typecnt + tzh_charcnt + 8*tzh_leapcnt +
+                   tzh_ttisstdcnt + tzh_ttisgmtcnt);
+        load_header(inf);
+        auto v2 = load_version(inf);
+        assert(v == v2);
+        skip_reserve(inf);
+#else  // defined(NDEBUG)
+        inf.ignore((4+1)*tzh_timecnt + 6*tzh_typecnt + tzh_charcnt + 8*tzh_leapcnt +
+                   tzh_ttisstdcnt + tzh_ttisgmtcnt + (4+1+15));
+#endif  // defined(NDEBUG)
+        load_counts(inf, tzh_ttisgmtcnt, tzh_ttisstdcnt, tzh_leapcnt,
+                         tzh_timecnt,    tzh_typecnt,    tzh_charcnt);
+        load_data<int64_t>(inf, tzh_leapcnt, tzh_timecnt, tzh_typecnt, tzh_charcnt);
+    }
+#if !MISSING_LEAP_SECONDS
+    if (tzh_leapcnt > 0)
+    {
+        auto& leap_seconds = get_tzdb_list().front().leaps;
+        auto itr = leap_seconds.begin();
+        auto l = itr->date();
+        seconds leap_count{0};
+        for (auto t = std::upper_bound(transitions_.begin(), transitions_.end(), l,
+                                       [](const sys_seconds& x, const transition& ct)
+                                       {
+                                           return x < ct.timepoint;
+                                       });
+                  t != transitions_.end(); ++t)
+        {
+            while (t->timepoint >= l)
+            {
+                ++leap_count;
+                if (++itr == leap_seconds.end())
+                    l = sys_days(max_year/max_day);
+                else
+                    l = itr->date() + leap_count;
+            }
+            t->timepoint -= leap_count;
+        }
+    }
+#endif  // !MISSING_LEAP_SECONDS
+    auto b = transitions_.begin();
+    auto i = transitions_.end();
+    if (i != b)
+    {
+        for (--i; i != b; --i)
+        {
+            if (i->info->offset == i[-1].info->offset &&
+                i->info->abbrev == i[-1].info->abbrev &&
+                i->info->is_dst == i[-1].info->is_dst)
+                i = transitions_.erase(i);
+        }
+    }
+}
+
+void
+time_zone::init() const
+{
+    std::call_once(*adjusted_, [this]() {const_cast<time_zone*>(this)->init_impl();});
+}
+
+sys_info
+time_zone::load_sys_info(std::vector<detail::transition>::const_iterator i) const
+{
+    using namespace std::chrono;
+    assert(!transitions_.empty());
+    assert(i != transitions_.begin());
+    sys_info r;
+    r.begin = i[-1].timepoint;
+    r.end = i != transitions_.end() ? i->timepoint :
+                                      sys_seconds(sys_days(year::max()/max_day));
+    r.offset = i[-1].info->offset;
+    r.save = i[-1].info->is_dst ? minutes{1} : minutes{0};
+    r.abbrev = i[-1].info->abbrev;
+    return r;
+}
+
+sys_info
+time_zone::get_info_impl(sys_seconds tp) const
+{
+    using namespace std;
+    init();
+    return load_sys_info(upper_bound(transitions_.begin(), transitions_.end(), tp,
+                                     [](const sys_seconds& x, const transition& t)
+                                     {
+                                         return x < t.timepoint;
+                                     }));
+}
+
+local_info
+time_zone::get_info_impl(local_seconds tp) const
+{
+    using namespace std::chrono;
+    init();
+    local_info i;
+    i.result = local_info::unique;
+    auto tr = upper_bound(transitions_.begin(), transitions_.end(), tp,
+                          [](const local_seconds& x, const transition& t)
+                          {
+                              return sys_seconds{x.time_since_epoch()} -
+                                                         t.info->offset < t.timepoint;
+                          });
+    i.first = load_sys_info(tr);
+    auto tps = sys_seconds{(tp - i.first.offset).time_since_epoch()};
+    if (tps < i.first.begin + days{1} && tr != transitions_.begin())
+    {
+        i.second = load_sys_info(--tr);
+        tps = sys_seconds{(tp - i.second.offset).time_since_epoch()};
+        if (tps < i.second.end)
+        {
+           i.result = local_info::ambiguous;
+           std::swap(i.first, i.second);
+        }
+        else
+        {
+            i.second = {};
+        }
+    }
+    else if (tps >= i.first.end && tr != transitions_.end())
+    {
+        i.second = load_sys_info(++tr);
+        tps = sys_seconds{(tp - i.second.offset).time_since_epoch()};
+        if (tps < i.second.begin)
+            i.result = local_info::nonexistent;
+        else
+            i.second = {};
+    }
+    return i;
+}
+
+std::ostream&
+operator<<(std::ostream& os, const time_zone& z)
+{
+    using namespace std::chrono;
+    z.init();
+    os << z.name_ << '\n';
+    os << "Initially:           ";
+    auto const& t = z.transitions_.front();
+    if (t.info->offset >= seconds{0})
+        os << '+';
+    os << make_time(t.info->offset);
+    if (t.info->is_dst > 0)
+        os << " daylight ";
+    else
+        os << " standard ";
+    os << t.info->abbrev << '\n';
+    for (auto i = std::next(z.transitions_.cbegin()); i < z.transitions_.cend(); ++i)
+        os << *i << '\n';
+    return os;
+}
+
+#if !MISSING_LEAP_SECONDS
+
+leap::leap(const sys_seconds& s, detail::undocumented)
+    : date_(s)
+{
+}
+
+#endif  // !MISSING_LEAP_SECONDS
+
+#else  // !USE_OS_TZDB
+
+time_zone::time_zone(const std::string& s, detail::undocumented)
+    : adjusted_(new std::once_flag{})
+{
+    try
+    {
+        using namespace date;
+        std::istringstream in(s);
+        in.exceptions(std::ios::failbit | std::ios::badbit);
+        std::string word;
+        in >> word >> name_;
+        parse_info(in);
+    }
+    catch (...)
+    {
+        std::cerr << s << '\n';
+        std::cerr << *this << '\n';
+        zonelets_.pop_back();
+        throw;
+    }
+}
+
+sys_info
+time_zone::get_info_impl(sys_seconds tp) const
+{
+    return get_info_impl(tp, static_cast<int>(tz::utc));
+}
+
+local_info
+time_zone::get_info_impl(local_seconds tp) const
+{
+    using namespace std::chrono;
+    local_info i{};
+    i.first = get_info_impl(sys_seconds{tp.time_since_epoch()}, static_cast<int>(tz::local));
+    auto tps = sys_seconds{(tp - i.first.offset).time_since_epoch()};
+    if (tps < i.first.begin)
+    {
+        i.second = std::move(i.first);
+        i.first = get_info_impl(i.second.begin - seconds{1}, static_cast<int>(tz::utc));
+        i.result = local_info::nonexistent;
+    }
+    else if (i.first.end - tps <= days{1})
+    {
+        i.second = get_info_impl(i.first.end, static_cast<int>(tz::utc));
+        tps = sys_seconds{(tp - i.second.offset).time_since_epoch()};
+        if (tps >= i.second.begin)
+            i.result = local_info::ambiguous;
+        else
+            i.second = {};
+    }
+    return i;
+}
+
+void
+time_zone::add(const std::string& s)
+{
+    try
+    {
+        std::istringstream in(s);
+        in.exceptions(std::ios::failbit | std::ios::badbit);
+        ws(in);
+        if (!in.eof() && in.peek() != '#')
+            parse_info(in);
+    }
+    catch (...)
+    {
+        std::cerr << s << '\n';
+        std::cerr << *this << '\n';
+        zonelets_.pop_back();
+        throw;
+    }
+}
+
+void
+time_zone::parse_info(std::istream& in)
+{
+    using namespace date;
+    using namespace std::chrono;
+    zonelets_.emplace_back();
+    auto& zonelet = zonelets_.back();
+    zonelet.gmtoff_ = parse_signed_time(in);
+    in >> zonelet.u.rule_;
+    if (zonelet.u.rule_ == "-")
+        zonelet.u.rule_.clear();
+    in >> zonelet.format_;
+    if (!in.eof())
+        ws(in);
+    if (in.eof() || in.peek() == '#')
+    {
+        zonelet.until_year_ = year::max();
+        zonelet.until_date_ = MonthDayTime(max_day, tz::utc);
+    }
+    else
+    {
+        int y;
+        in >> y;
+        zonelet.until_year_ = year{y};
+        in >> zonelet.until_date_;
+        zonelet.until_date_.canonicalize(zonelet.until_year_);
+    }
+    if ((zonelet.until_year_ < min_year) ||
+            (zonelets_.size() > 1 && zonelets_.end()[-2].until_year_ > max_year))
+        zonelets_.pop_back();
+}
+
+void
+time_zone::adjust_infos(const std::vector<Rule>& rules)
+{
+    using namespace std::chrono;
+    using namespace date;
+    const zonelet* prev_zonelet = nullptr;
+    for (auto& z : zonelets_)
+    {
+        std::pair<const Rule*, const Rule*> eqr{};
+        std::istringstream in;
+        in.exceptions(std::ios::failbit | std::ios::badbit);
+        // Classify info as rule-based, has save, or neither
+        if (!z.u.rule_.empty())
+        {
+            // Find out if this zonelet has a rule or a save
+            eqr = std::equal_range(rules.data(), rules.data() + rules.size(), z.u.rule_);
+            if (eqr.first == eqr.second)
+            {
+                // The rule doesn't exist.  Assume this is a save
+                try
+                {
+                    using namespace std::chrono;
+                    using string = std::string;
+                    in.str(z.u.rule_);
+                    auto tmp = duration_cast<minutes>(parse_signed_time(in));
+#if !defined(_MSC_VER) || (_MSC_VER >= 1900)
+                    z.u.rule_.~string();
+                    z.tag_ = zonelet::has_save;
+                    ::new(&z.u.save_) minutes(tmp);
+#else
+                    z.u.rule_.clear();
+                    z.tag_ = zonelet::has_save;
+                    z.u.save_ = tmp;
+#endif
+                }
+                catch (...)
+                {
+                    std::cerr << name_ << " : " << z.u.rule_ << '\n';
+                    throw;
+                }
+            }
+        }
+        else
+        {
+            // This zone::zonelet has no rule and no save
+            z.tag_ = zonelet::is_empty;
+        }
+
+        minutes final_save{0};
+        if (z.tag_ == zonelet::has_save)
+        {
+            final_save = z.u.save_;
+        }
+        else if (z.tag_ == zonelet::has_rule)
+        {
+            z.last_rule_ = find_rule_for_zone(eqr, z.until_year_, z.gmtoff_,
+                                              z.until_date_);
+            if (z.last_rule_.first != nullptr)
+                final_save = z.last_rule_.first->save();
+        }
+        z.until_utc_ = z.until_date_.to_sys(z.until_year_, z.gmtoff_, final_save);
+        z.until_std_ = local_seconds{z.until_utc_.time_since_epoch()} + z.gmtoff_;
+        z.until_loc_ = z.until_std_ + final_save;
+
+        if (z.tag_ == zonelet::has_rule)
+        {
+            if (prev_zonelet != nullptr)
+            {
+                z.first_rule_ = find_rule_for_zone(eqr, prev_zonelet->until_utc_,
+                                                        prev_zonelet->until_std_,
+                                                        prev_zonelet->until_loc_);
+                if (z.first_rule_.first != nullptr)
+                {
+                    z.initial_save_ = z.first_rule_.first->save();
+                    z.initial_abbrev_ = z.first_rule_.first->abbrev();
+                    if (z.first_rule_ != z.last_rule_)
+                    {
+                        z.first_rule_ = find_next_rule(eqr.first, eqr.second,
+                                                       z.first_rule_.first,
+                                                       z.first_rule_.second);
+                    }
+                    else
+                    {
+                        z.first_rule_ = std::make_pair(nullptr, year::min());
+                        z.last_rule_ = std::make_pair(nullptr, year::max());
+                    }
+                }
+            }
+            if (z.first_rule_.first == nullptr && z.last_rule_.first != nullptr)
+            {
+                z.first_rule_ = std::make_pair(eqr.first, eqr.first->starting_year());
+                z.initial_abbrev_ = find_first_std_rule(eqr)->abbrev();
+            }
+        }
+
+#ifndef NDEBUG
+        if (z.first_rule_.first == nullptr)
+        {
+            assert(z.first_rule_.second == year::min());
+            assert(z.last_rule_.first == nullptr);
+            assert(z.last_rule_.second == year::max());
+        }
+        else
+        {
+            assert(z.last_rule_.first != nullptr);
+        }
+#endif
+        prev_zonelet = &z;
+    }
+}
+
+static
+std::string
+format_abbrev(std::string format, const std::string& variable, std::chrono::seconds off,
+                                                               std::chrono::minutes save)
+{
+    using namespace std::chrono;
+    auto k = format.find("%s");
+    if (k != std::string::npos)
+    {
+        format.replace(k, 2, variable);
+    }
+    else
+    {
+        k = format.find('/');
+        if (k != std::string::npos)
+        {
+            if (save == minutes{0})
+                format.erase(k);
+            else
+                format.erase(0, k+1);
+        }
+        else
+        {
+            k = format.find("%z");
+            if (k != std::string::npos)
+            {
+                std::string temp;
+                if (off < seconds{0})
+                {
+                    temp = '-';
+                    off = -off;
+                }
+                else
+                    temp = '+';
+                auto h = date::floor<hours>(off);
+                off -= h;
+                if (h < hours{10})
+                    temp += '0';
+                temp += std::to_string(h.count());
+                if (off > seconds{0})
+                {
+                    auto m = date::floor<minutes>(off);
+                    off -= m;
+                    if (m < minutes{10})
+                        temp += '0';
+                    temp += std::to_string(m.count());
+                    if (off > seconds{0})
+                    {
+                        if (off < seconds{10})
+                            temp += '0';
+                        temp += std::to_string(off.count());
+                    }
+                }
+                format.replace(k, 2, temp);
+            }
+        }
+    }
+    return format;
+}
+
+sys_info
+time_zone::get_info_impl(sys_seconds tp, int tz_int) const
+{
+    using namespace std::chrono;
+    using namespace date;
+    tz timezone = static_cast<tz>(tz_int);
+    assert(timezone != tz::standard);
+    auto y = year_month_day(floor<days>(tp)).year();
+    if (y < min_year || y > max_year)
+        throw std::runtime_error("The year " + std::to_string(static_cast<int>(y)) +
+            " is out of range:[" + std::to_string(static_cast<int>(min_year)) + ", "
+                                 + std::to_string(static_cast<int>(max_year)) + "]");
+    std::call_once(*adjusted_,
+                   [this]()
+                   {
+                       const_cast<time_zone*>(this)->adjust_infos(get_tzdb().rules);
+                   });
+    auto i = std::upper_bound(zonelets_.begin(), zonelets_.end(), tp,
+        [timezone](sys_seconds t, const zonelet& zl)
+        {
+            return timezone == tz::utc ? t < zl.until_utc_ :
+                                         t < sys_seconds{zl.until_loc_.time_since_epoch()};
+        });
+
+    sys_info r{};
+    if (i != zonelets_.end())
+    {
+        if (i->tag_ == zonelet::has_save)
+        {
+            if (i != zonelets_.begin())
+                r.begin = i[-1].until_utc_;
+            else
+                r.begin = sys_days(year::min()/min_day);
+            r.end = i->until_utc_;
+            r.offset = i->gmtoff_ + i->u.save_;
+            r.save = i->u.save_;
+        }
+        else if (i->u.rule_.empty())
+        {
+            if (i != zonelets_.begin())
+                r.begin = i[-1].until_utc_;
+            else
+                r.begin = sys_days(year::min()/min_day);
+            r.end = i->until_utc_;
+            r.offset = i->gmtoff_;
+        }
+        else
+        {
+            r = find_rule(i->first_rule_, i->last_rule_, y, i->gmtoff_,
+                          MonthDayTime(local_seconds{tp.time_since_epoch()}, timezone),
+                          i->initial_save_, i->initial_abbrev_);
+            r.offset = i->gmtoff_ + r.save;
+            if (i != zonelets_.begin() && r.begin < i[-1].until_utc_)
+                r.begin = i[-1].until_utc_;
+            if (r.end > i->until_utc_)
+                r.end = i->until_utc_;
+        }
+        r.abbrev = format_abbrev(i->format_, r.abbrev, r.offset, r.save);
+        assert(r.begin < r.end);
+    }
+    return r;
+}
+
+std::ostream&
+operator<<(std::ostream& os, const time_zone& z)
+{
+    using namespace date;
+    using namespace std::chrono;
+    detail::save_ostream<char> _(os);
+    os.fill(' ');
+    os.flags(std::ios::dec | std::ios::left);
+    std::call_once(*z.adjusted_,
+                   [&z]()
+                   {
+                       const_cast<time_zone&>(z).adjust_infos(get_tzdb().rules);
+                   });
+    os.width(35);
+    os << z.name_;
+    std::string indent;
+    for (auto const& s : z.zonelets_)
+    {
+        os << indent;
+        if (s.gmtoff_ >= seconds{0})
+            os << ' ';
+        os << make_time(s.gmtoff_) << "   ";
+        os.width(15);
+        if (s.tag_ != zonelet::has_save)
+            os << s.u.rule_;
+        else
+        {
+            std::ostringstream tmp;
+            tmp << make_time(s.u.save_);
+            os <<  tmp.str();
+        }
+        os.width(8);
+        os << s.format_ << "   ";
+        os << s.until_year_ << ' ' << s.until_date_;
+        os << "   " << s.until_utc_ << " UTC";
+        os << "   " << s.until_std_ << " STD";
+        os << "   " << s.until_loc_;
+        os << "   " << make_time(s.initial_save_);
+        os << "   " << s.initial_abbrev_;
+        if (s.first_rule_.first != nullptr)
+            os << "   {" << *s.first_rule_.first << ", " << s.first_rule_.second << '}';
+        else
+            os << "   {" << "nullptr" << ", " << s.first_rule_.second << '}';
+        if (s.last_rule_.first != nullptr)
+            os << "   {" << *s.last_rule_.first << ", " << s.last_rule_.second << '}';
+        else
+            os << "   {" << "nullptr" << ", " << s.last_rule_.second << '}';
+        os << '\n';
+        if (indent.empty())
+            indent = std::string(35, ' ');
+    }
+    return os;
+}
+
+#endif  // !USE_OS_TZDB
+
+#if !MISSING_LEAP_SECONDS
+
+std::ostream&
+operator<<(std::ostream& os, const leap& x)
+{
+    using namespace date;
+    return os << x.date_ << "  +";
+}
+
+#endif  // !MISSING_LEAP_SECONDS
+
+#if USE_OS_TZDB
+
+# ifdef __APPLE__
+static
+std::string
+get_version()
+{
+    using namespace std;
+    auto path = get_tz_dir() + string("/+VERSION");
+    ifstream in{path};
+    string version;
+    in >> version;
+    if (in.fail())
+        throw std::runtime_error("Unable to get Timezone database version from " + path);
+    return version;
+}
+# endif
+
+static
+std::unique_ptr<tzdb>
+init_tzdb()
+{
+    std::unique_ptr<tzdb> db(new tzdb);
+
+    //Iterate through folders
+    std::queue<std::string> subfolders;
+    subfolders.emplace(get_tz_dir());
+    struct dirent* d;
+    struct stat s;
+    while (!subfolders.empty())
+    {
+        auto dirname = std::move(subfolders.front());
+        subfolders.pop();
+        auto dir = opendir(dirname.c_str());
+        if (!dir)
+            continue;
+        while ((d = readdir(dir)) != nullptr)
+        {
+            // Ignore these files:
+            if (d->d_name[0]                      == '.'    || // curdir, prevdir, hidden
+                memcmp(d->d_name, "posix", 5)     == 0      || // starts with posix
+                strcmp(d->d_name, "Factory")      == 0      ||
+                strcmp(d->d_name, "iso3166.tab")  == 0      ||
+                strcmp(d->d_name, "right")        == 0      ||
+                strcmp(d->d_name, "+VERSION")     == 0      ||
+                strcmp(d->d_name, "zone.tab")     == 0      ||
+                strcmp(d->d_name, "zone1970.tab") == 0      ||
+                strcmp(d->d_name, "tzdata.zi")    == 0      ||
+                strcmp(d->d_name, "leapseconds")  == 0      ||
+                strcmp(d->d_name, "leap-seconds.list") == 0   )
+                continue;
+            auto subname = dirname + folder_delimiter + d->d_name;
+            if(stat(subname.c_str(), &s) == 0)
+            {
+                if(S_ISDIR(s.st_mode))
+                {
+                    if(!S_ISLNK(s.st_mode))
+                    {
+                        subfolders.push(subname);
+                    }
+                }
+                else
+                {
+                    db->zones.emplace_back(subname.substr(get_tz_dir().size()+1),
+                                           detail::undocumented{});
+                }
+            }
+        }
+        closedir(dir);
+    }
+    db->zones.shrink_to_fit();
+    std::sort(db->zones.begin(), db->zones.end());
+#  if !MISSING_LEAP_SECONDS
+    std::ifstream in(get_tz_dir() + std::string(1, folder_delimiter) + "right/UTC",
+                     std::ios_base::binary);
+    if (in)
+    {
+        in.exceptions(std::ios::failbit | std::ios::badbit);
+        db->leaps = load_just_leaps(in);
+    }
+    else
+    {
+        in.clear();
+        in.open(get_tz_dir() + std::string(1, folder_delimiter) +
+                "UTC", std::ios_base::binary);
+        if (!in)
+            throw std::runtime_error("Unable to extract leap second information");
+        in.exceptions(std::ios::failbit | std::ios::badbit);
+        db->leaps = load_just_leaps(in);
+    }
+#  endif  // !MISSING_LEAP_SECONDS
+#  ifdef __APPLE__
+    db->version = get_version();
+#  endif
+    return db;
+}
+
+#else  // !USE_OS_TZDB
+
+// link
+
+link::link(const std::string& s)
+{
+    using namespace date;
+    std::istringstream in(s);
+    in.exceptions(std::ios::failbit | std::ios::badbit);
+    std::string word;
+    in >> word >> target_ >> name_;
+}
+
+std::ostream&
+operator<<(std::ostream& os, const link& x)
+{
+    using namespace date;
+    detail::save_ostream<char> _(os);
+    os.fill(' ');
+    os.flags(std::ios::dec | std::ios::left);
+    os.width(35);
+    return os << x.name_ << " --> " << x.target_;
+}
+
+// leap
+
+leap::leap(const std::string& s, detail::undocumented)
+{
+    using namespace date;
+    std::istringstream in(s);
+    in.exceptions(std::ios::failbit | std::ios::badbit);
+    std::string word;
+    int y;
+    MonthDayTime date;
+    in >> word >> y >> date;
+    date_ = date.to_time_point(year(y));
+}
+
+static
+bool
+file_exists(const std::string& filename)
+{
+#ifdef _WIN32
+    return ::_access(filename.c_str(), 0) == 0;
+#else
+    return ::access(filename.c_str(), F_OK) == 0;
+#endif
+}
+
+#if HAS_REMOTE_API
+
+// CURL tools
+
+static
+int
+curl_global()
+{
+    if (::curl_global_init(CURL_GLOBAL_DEFAULT) != 0)
+        throw std::runtime_error("CURL global initialization failed");
+    return 0;
+}
+
+namespace
+{
+
+struct curl_deleter
+{
+    void operator()(CURL* p) const
+    {
+        ::curl_easy_cleanup(p);
+    }
+};
+
+}  // unnamed namespace
+
+static
+std::unique_ptr<CURL, curl_deleter>
+curl_init()
+{
+    static const auto curl_is_now_initiailized = curl_global();
+    (void)curl_is_now_initiailized;
+    return std::unique_ptr<CURL, curl_deleter>{::curl_easy_init()};
+}
+
+static
+bool
+download_to_string(const std::string& url, std::string& str)
+{
+    str.clear();
+    auto curl = curl_init();
+    if (!curl)
+        return false;
+    std::string version;
+    curl_easy_setopt(curl.get(), CURLOPT_USERAGENT, "curl");
+    curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str());
+    curl_write_callback write_cb = [](char* contents, std::size_t size, std::size_t nmemb,
+                                      void* userp) -> std::size_t
+    {
+        auto& userstr = *static_cast<std::string*>(userp);
+        auto realsize = size * nmemb;
+        userstr.append(contents, realsize);
+        return realsize;
+    };
+    curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, write_cb);
+    curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &str);
+    curl_easy_setopt(curl.get(), CURLOPT_SSL_VERIFYPEER, false);
+    auto res = curl_easy_perform(curl.get());
+    return (res == CURLE_OK);
+}
+
+namespace
+{
+    enum class download_file_options { binary, text };
+}
+
+static
+bool
+download_to_file(const std::string& url, const std::string& local_filename,
+                 download_file_options opts)
+{
+    auto curl = curl_init();
+    if (!curl)
+        return false;
+    curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str());
+    curl_easy_setopt(curl.get(), CURLOPT_SSL_VERIFYPEER, false);
+    curl_write_callback write_cb = [](char* contents, std::size_t size, std::size_t nmemb,
+                                      void* userp) -> std::size_t
+    {
+        auto& of = *static_cast<std::ofstream*>(userp);
+        auto realsize = size * nmemb;
+        of.write(contents, static_cast<std::streamsize>(realsize));
+        return realsize;
+    };
+    curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, write_cb);
+    decltype(curl_easy_perform(curl.get())) res;
+    {
+        std::ofstream of(local_filename,
+                         opts == download_file_options::binary ?
+                             std::ofstream::out | std::ofstream::binary :
+                             std::ofstream::out);
+        of.exceptions(std::ios::badbit);
+        curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &of);
+        res = curl_easy_perform(curl.get());
+    }
+    return res == CURLE_OK;
+}
+
+std::string
+remote_version()
+{
+    std::string version;
+    std::string str;
+    if (download_to_string("https://www.iana.org/time-zones", str))
+    {
+        CONSTDATA char db[] = "/time-zones/releases/tzdata";
+        CONSTDATA auto db_size = sizeof(db) - 1;
+        auto p = str.find(db, 0, db_size);
+        const int ver_str_len = 5;
+        if (p != std::string::npos && p + (db_size + ver_str_len) <= str.size())
+            version = str.substr(p + db_size, ver_str_len);
+    }
+    return version;
+}
+
+
+// TODO! Using system() create a process and a console window.
+// This is useful to see what errors may occur but is slow and distracting.
+// Consider implementing this functionality more directly, such as
+// using _mkdir and CreateProcess etc.
+// But use the current means now as matches Unix implementations and while
+// in proof of concept / testing phase.
+// TODO! Use <filesystem> eventually.
+static
+bool
+remove_folder_and_subfolders(const std::string& folder)
+{
+#  ifdef _WIN32
+#    if USE_SHELL_API
+    // Delete the folder contents by deleting the folder.
+    std::string cmd = "rd /s /q \"";
+    cmd += folder;
+    cmd += '\"';
+    return std::system(cmd.c_str()) == EXIT_SUCCESS;
+#    else  // !USE_SHELL_API
+    // Create a buffer containing the path to delete. It must be terminated
+    // by two nuls. Who designs these API's...
+    std::vector<char> from;
+    from.assign(folder.begin(), folder.end());
+    from.push_back('\0');
+    from.push_back('\0');
+    SHFILEOPSTRUCT fo{}; // Zero initialize.
+    fo.wFunc = FO_DELETE;
+    fo.pFrom = from.data();
+    fo.fFlags = FOF_NO_UI;
+    int ret = SHFileOperation(&fo);
+    if (ret == 0 && !fo.fAnyOperationsAborted)
+        return true;
+    return false;
+#    endif  // !USE_SHELL_API
+#  else   // !_WIN32
+#    if USE_SHELL_API
+    return std::system(("rm -R " + folder).c_str()) == EXIT_SUCCESS;
+#    else // !USE_SHELL_API
+    struct dir_deleter {
+        dir_deleter() {}
+        void operator()(DIR* d) const
+        {
+            if (d != nullptr)
+            {
+                int result = closedir(d);
+                assert(result == 0);
+            }
+        }
+    };
+    using closedir_ptr = std::unique_ptr<DIR, dir_deleter>;
+
+    std::string filename;
+    struct stat statbuf;
+    std::size_t folder_len = folder.length();
+    struct dirent* p = nullptr;
+
+    closedir_ptr d(opendir(folder.c_str()));
+    bool r = d.get() != nullptr;
+    while (r && (p=readdir(d.get())) != nullptr)
+    {
+        if (strcmp(p->d_name, ".") == 0 || strcmp(p->d_name, "..") == 0)
+           continue;
+
+        // + 2 for path delimiter and nul terminator.
+        std::size_t buf_len = folder_len + strlen(p->d_name) + 2;
+        filename.resize(buf_len);
+        std::size_t path_len = static_cast<std::size_t>(
+            snprintf(&filename[0], buf_len, "%s/%s", folder.c_str(), p->d_name));
+        assert(path_len == buf_len - 1);
+        filename.resize(path_len);
+
+        if (stat(filename.c_str(), &statbuf) == 0)
+            r = S_ISDIR(statbuf.st_mode)
+              ? remove_folder_and_subfolders(filename)
+              : unlink(filename.c_str()) == 0;
+    }
+    d.reset();
+
+    if (r)
+        r = rmdir(folder.c_str()) == 0;
+
+    return r;
+#    endif // !USE_SHELL_API
+#  endif  // !_WIN32
+}
+
+static
+bool
+make_directory(const std::string& folder)
+{
+#  ifdef _WIN32
+#    if USE_SHELL_API
+    // Re-create the folder.
+    std::string cmd = "mkdir \"";
+    cmd += folder;
+    cmd += '\"';
+    return std::system(cmd.c_str()) == EXIT_SUCCESS;
+#    else  // !USE_SHELL_API
+    return _mkdir(folder.c_str()) == 0;
+#    endif // !USE_SHELL_API
+#  else  // !_WIN32
+#    if USE_SHELL_API
+    return std::system(("mkdir -p " + folder).c_str()) == EXIT_SUCCESS;
+#    else  // !USE_SHELL_API
+    return mkdir(folder.c_str(), 0777) == 0;
+#    endif  // !USE_SHELL_API
+#  endif  // !_WIN32
+}
+
+static
+bool
+delete_file(const std::string& file)
+{
+#  ifdef _WIN32
+#    if USE_SHELL_API
+    std::string cmd = "del \"";
+    cmd += file;
+    cmd += '\"';
+    return std::system(cmd.c_str()) == 0;
+#    else  // !USE_SHELL_API
+    return _unlink(file.c_str()) == 0;
+#    endif // !USE_SHELL_API
+#  else  // !_WIN32
+#    if USE_SHELL_API
+    return std::system(("rm " + file).c_str()) == EXIT_SUCCESS;
+#    else // !USE_SHELL_API
+    return unlink(file.c_str()) == 0;
+#    endif // !USE_SHELL_API
+#  endif  // !_WIN32
+}
+
+#  ifdef _WIN32
+
+static
+bool
+move_file(const std::string& from, const std::string& to)
+{
+#    if USE_SHELL_API
+    std::string cmd = "move \"";
+    cmd += from;
+    cmd += "\" \"";
+    cmd += to;
+    cmd += '\"';
+    return std::system(cmd.c_str()) == EXIT_SUCCESS;
+#    else  // !USE_SHELL_API
+    return !!::MoveFile(from.c_str(), to.c_str());
+#    endif // !USE_SHELL_API
+}
+
+// Usually something like "c:\Program Files".
+static
+std::string
+get_program_folder()
+{
+    return get_known_folder(FOLDERID_ProgramFiles);
+}
+
+// Note folder can and usually does contain spaces.
+static
+std::string
+get_unzip_program()
+{
+    std::string path;
+
+    // 7-Zip appears to note its location in the registry.
+    // If that doesn't work, fall through and take a guess, but it will likely be wrong.
+    HKEY hKey = nullptr;
+    if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\7-Zip", 0, KEY_READ, &hKey) == ERROR_SUCCESS)
+    {
+        char value_buffer[MAX_PATH + 1]; // fyi 260 at time of writing.
+        // in/out parameter. Documentation say that size is a count of bytes not chars.
+        DWORD size = sizeof(value_buffer) - sizeof(value_buffer[0]);
+        DWORD tzi_type = REG_SZ;
+        // Testing shows Path key value is "C:\Program Files\7-Zip\" i.e. always with trailing \.
+        bool got_value = (RegQueryValueExA(hKey, "Path", nullptr, &tzi_type,
+            reinterpret_cast<LPBYTE>(value_buffer), &size) == ERROR_SUCCESS);
+        RegCloseKey(hKey); // Close now incase of throw later.
+        if (got_value)
+        {
+            // Function does not guarantee to null terminate.
+            value_buffer[size / sizeof(value_buffer[0])] = '\0';
+            path = value_buffer;
+            if (!path.empty())
+            {
+                path += "7z.exe";
+                return path;
+            }
+        }
+    }
+    path += get_program_folder();
+    path += folder_delimiter;
+    path += "7-Zip\\7z.exe";
+    return path;
+}
+
+#    if !USE_SHELL_API
+static
+int
+run_program(const std::string& command)
+{
+    STARTUPINFO si{};
+    si.cb = sizeof(si);
+    PROCESS_INFORMATION pi{};
+
+    // Allegedly CreateProcess overwrites the command line. Ugh.
+    std::string mutable_command(command);
+    if (CreateProcess(nullptr, &mutable_command[0],
+        nullptr, nullptr, FALSE, CREATE_NO_WINDOW, nullptr, nullptr, &si, &pi))
+    {
+        WaitForSingleObject(pi.hProcess, INFINITE);
+        DWORD exit_code;
+        bool got_exit_code = !!GetExitCodeProcess(pi.hProcess, &exit_code);
+        CloseHandle(pi.hProcess);
+        CloseHandle(pi.hThread);
+        // Not 100% sure about this still active thing is correct,
+        // but I'm going with it because I *think* WaitForSingleObject might
+        // return in some cases without INFINITE-ly waiting.
+        // But why/wouldn't GetExitCodeProcess return false in that case?
+        if (got_exit_code && exit_code != STILL_ACTIVE)
+            return static_cast<int>(exit_code);
+    }
+    return EXIT_FAILURE;
+}
+#    endif // !USE_SHELL_API
+
+static
+std::string
+get_download_tar_file(const std::string& version)
+{
+    auto file = get_install();
+    file += folder_delimiter;
+    file += "tzdata";
+    file += version;
+    file += ".tar";
+    return file;
+}
+
+static
+bool
+extract_gz_file(const std::string& version, const std::string& gz_file,
+                const std::string& dest_folder)
+{
+    auto unzip_prog = get_unzip_program();
+    bool unzip_result = false;
+    // Use the unzip program to extract the tar file from the archive.
+
+    // Aim to create a string like:
+    // "C:\Program Files\7-Zip\7z.exe" x "C:\Users\SomeUser\Downloads\tzdata2016d.tar.gz"
+    //     -o"C:\Users\SomeUser\Downloads\tzdata"
+    std::string cmd;
+    cmd = '\"';
+    cmd += unzip_prog;
+    cmd += "\" x \"";
+    cmd += gz_file;
+    cmd += "\" -o\"";
+    cmd += dest_folder;
+    cmd += '\"';
+
+#    if USE_SHELL_API
+    // When using shelling out with std::system() extra quotes are required around the
+    // whole command. It's weird but necessary it seems, see:
+    // http://stackoverflow.com/q/27975969/576911
+
+    cmd = "\"" + cmd + "\"";
+    if (std::system(cmd.c_str()) == EXIT_SUCCESS)
+        unzip_result = true;
+#    else  // !USE_SHELL_API
+    if (run_program(cmd) == EXIT_SUCCESS)
+        unzip_result = true;
+#    endif // !USE_SHELL_API
+    if (unzip_result)
+        delete_file(gz_file);
+
+    // Use the unzip program extract the data from the tar file that was
+    // just extracted from the archive.
+    auto tar_file = get_download_tar_file(version);
+    cmd = '\"';
+    cmd += unzip_prog;
+    cmd += "\" x \"";
+    cmd += tar_file;
+    cmd += "\" -o\"";
+    cmd += get_install();
+    cmd += '\"';
+#    if USE_SHELL_API
+    cmd = "\"" + cmd + "\"";
+    if (std::system(cmd.c_str()) == EXIT_SUCCESS)
+        unzip_result = true;
+#    else  // !USE_SHELL_API
+    if (run_program(cmd) == EXIT_SUCCESS)
+        unzip_result = true;
+#    endif // !USE_SHELL_API
+
+    if (unzip_result)
+        delete_file(tar_file);
+
+    return unzip_result;
+}
+
+static
+std::string
+get_download_mapping_file(const std::string& version)
+{
+    auto file = get_install() + version + "windowsZones.xml";
+    return file;
+}
+
+#  else  // !_WIN32
+
+#    if !USE_SHELL_API
+static
+int
+run_program(const char* prog, const char*const args[])
+{
+    pid_t pid = fork();
+    if (pid == -1) // Child failed to start.
+        return EXIT_FAILURE;
+
+    if (pid != 0)
+    {
+        // We are in the parent. Child started. Wait for it.
+        pid_t ret;
+        int status;
+        while ((ret = waitpid(pid, &status, 0)) == -1)
+        {
+            if (errno != EINTR)
+                break;
+        }
+        if (ret != -1)
+        {
+            if (WIFEXITED(status))
+                return WEXITSTATUS(status);
+        }
+        printf("Child issues!\n");
+
+        return EXIT_FAILURE; // Not sure what status of child is.
+    }
+    else // We are in the child process. Start the program the parent wants to run.
+    {
+
+        if (execv(prog, const_cast<char**>(args)) == -1) // Does not return.
+        {
+            perror("unreachable 0\n");
+            _Exit(127);
+        }
+        printf("unreachable 2\n");
+    }
+    printf("unreachable 2\n");
+    // Unreachable.
+    assert(false);
+    exit(EXIT_FAILURE);
+    return EXIT_FAILURE;
+}
+#    endif // !USE_SHELL_API
+
+static
+bool
+extract_gz_file(const std::string&, const std::string& gz_file, const std::string&)
+{
+#    if USE_SHELL_API
+    bool unzipped = std::system(("tar -xzf " + gz_file + " -C " + get_install()).c_str()) == EXIT_SUCCESS;
+#    else  // !USE_SHELL_API
+    const char prog[] = {"/usr/bin/tar"};
+    const char*const args[] =
+    {
+        prog, "-xzf", gz_file.c_str(), "-C", get_install().c_str(), nullptr
+    };
+    bool unzipped = (run_program(prog, args) == EXIT_SUCCESS);
+#    endif // !USE_SHELL_API
+    if (unzipped)
+    {
+        delete_file(gz_file);
+        return true;
+    }
+    return false;
+}
+
+#  endif // !_WIN32
+
+bool
+remote_download(const std::string& version)
+{
+    assert(!version.empty());
+
+#  ifdef _WIN32
+    // Download folder should be always available for Windows
+#  else  // !_WIN32
+    // Create download folder if it does not exist on UNIX system
+    auto download_folder = get_install();
+    if (!file_exists(download_folder))
+    {
+        if (!make_directory(download_folder))
+            return false;
+    }
+#  endif  // _WIN32
+
+    auto url = "https://data.iana.org/time-zones/releases/tzdata" + version +
+               ".tar.gz";
+    bool result = download_to_file(url, get_download_gz_file(version),
+                                   download_file_options::binary);
+#  ifdef _WIN32
+    if (result)
+    {
+        auto mapping_file = get_download_mapping_file(version);
+        result = download_to_file(
+			"https://raw.githubusercontent.com/unicode-org/cldr/master/"
+			"common/supplemental/windowsZones.xml",
+            mapping_file, download_file_options::text);
+    }
+#  endif  // _WIN32
+    return result;
+}
+
+bool
+remote_install(const std::string& version)
+{
+    auto success = false;
+    assert(!version.empty());
+
+    std::string install = get_install();
+    auto gz_file = get_download_gz_file(version);
+    if (file_exists(gz_file))
+    {
+        if (file_exists(install))
+            remove_folder_and_subfolders(install);
+        if (make_directory(install))
+        {
+            if (extract_gz_file(version, gz_file, install))
+                success = true;
+#  ifdef _WIN32
+            auto mapping_file_source = get_download_mapping_file(version);
+            auto mapping_file_dest = get_install();
+            mapping_file_dest += folder_delimiter;
+            mapping_file_dest += "windowsZones.xml";
+            if (!move_file(mapping_file_source, mapping_file_dest))
+                success = false;
+#  endif  // _WIN32
+        }
+    }
+    return success;
+}
+
+#endif  // HAS_REMOTE_API
+
+static
+std::string
+get_version(const std::string& path)
+{
+    std::string version;
+    std::ifstream infile(path + "version");
+    if (infile.is_open())
+    {
+        infile >> version;
+        if (!infile.fail())
+            return version;
+    }
+    else
+    {
+        infile.open(path + "NEWS");
+        while (infile)
+        {
+            infile >> version;
+            if (version == "Release")
+            {
+                infile >> version;
+                return version;
+            }
+        }
+    }
+    throw std::runtime_error("Unable to get Timezone database version from " + path);
+}
+
+static
+std::unique_ptr<tzdb>
+init_tzdb()
+{
+    using namespace date;
+    const std::string install = get_install();
+    const std::string path = install + folder_delimiter;
+    std::string line;
+    bool continue_zone = false;
+    std::unique_ptr<tzdb> db(new tzdb);
+
+#if AUTO_DOWNLOAD
+    if (!file_exists(install))
+    {
+        auto rv = remote_version();
+        if (!rv.empty() && remote_download(rv))
+        {
+            if (!remote_install(rv))
+            {
+                std::string msg = "Timezone database version \"";
+                msg += rv;
+                msg += "\" did not install correctly to \"";
+                msg += install;
+                msg += "\"";
+                throw std::runtime_error(msg);
+            }
+        }
+        if (!file_exists(install))
+        {
+            std::string msg = "Timezone database not found at \"";
+            msg += install;
+            msg += "\"";
+            throw std::runtime_error(msg);
+        }
+        db->version = get_version(path);
+    }
+    else
+    {
+        db->version = get_version(path);
+        auto rv = remote_version();
+        if (!rv.empty() && db->version != rv)
+        {
+            if (remote_download(rv))
+            {
+                remote_install(rv);
+                db->version = get_version(path);
+            }
+        }
+    }
+#else  // !AUTO_DOWNLOAD
+    if (!file_exists(install))
+    {
+        std::string msg = "Timezone database not found at \"";
+        msg += install;
+        msg += "\"";
+        throw std::runtime_error(msg);
+    }
+    db->version = get_version(path);
+#endif  // !AUTO_DOWNLOAD
+
+    CONSTDATA char*const files[] =
+    {
+        "africa", "antarctica", "asia", "australasia", "backward", "etcetera", "europe",
+        "pacificnew", "northamerica", "southamerica", "systemv", "leapseconds"
+    };
+
+    for (const auto& filename : files)
+    {
+        std::ifstream infile(path + filename);
+        while (infile)
+        {
+            std::getline(infile, line);
+            if (!line.empty() && line[0] != '#')
+            {
+                std::istringstream in(line);
+                std::string word;
+                in >> word;
+                if (word == "Rule")
+                {
+                    db->rules.push_back(Rule(line));
+                    continue_zone = false;
+                }
+                else if (word == "Link")
+                {
+                    db->links.push_back(link(line));
+                    continue_zone = false;
+                }
+                else if (word == "Leap")
+                {
+                    db->leaps.push_back(leap(line, detail::undocumented{}));
+                    continue_zone = false;
+                }
+                else if (word == "Zone")
+                {
+                    db->zones.push_back(time_zone(line, detail::undocumented{}));
+                    continue_zone = true;
+                }
+                else if (line[0] == '\t' && continue_zone)
+                {
+                    db->zones.back().add(line);
+                }
+                else
+                {
+                    std::cerr << line << '\n';
+                }
+            }
+        }
+    }
+    std::sort(db->rules.begin(), db->rules.end());
+    Rule::split_overlaps(db->rules);
+    std::sort(db->zones.begin(), db->zones.end());
+    db->zones.shrink_to_fit();
+    std::sort(db->links.begin(), db->links.end());
+    db->links.shrink_to_fit();
+    std::sort(db->leaps.begin(), db->leaps.end());
+    db->leaps.shrink_to_fit();
+
+#ifdef _WIN32
+    std::string mapping_file = get_install() + folder_delimiter + "windowsZones.xml";
+    db->mappings = load_timezone_mappings_from_xml_file(mapping_file);
+    sort_zone_mappings(db->mappings);
+#endif // _WIN32
+
+    return db;
+}
+
+const tzdb&
+reload_tzdb()
+{
+#if AUTO_DOWNLOAD
+    auto const& v = get_tzdb_list().front().version;
+    if (!v.empty() && v == remote_version())
+        return get_tzdb_list().front();
+#endif  // AUTO_DOWNLOAD
+    tzdb_list::undocumented_helper::push_front(get_tzdb_list(), init_tzdb().release());
+    return get_tzdb_list().front();
+}
+
+#endif  // !USE_OS_TZDB
+
+const tzdb&
+get_tzdb()
+{
+    return get_tzdb_list().front();
+}
+
+const time_zone*
+#if HAS_STRING_VIEW
+tzdb::locate_zone(std::string_view tz_name) const
+#else
+tzdb::locate_zone(const std::string& tz_name) const
+#endif
+{
+    auto zi = std::lower_bound(zones.begin(), zones.end(), tz_name,
+#if HAS_STRING_VIEW
+        [](const time_zone& z, const std::string_view& nm)
+#else
+        [](const time_zone& z, const std::string& nm)
+#endif
+        {
+            return z.name() < nm;
+        });
+    if (zi == zones.end() || zi->name() != tz_name)
+    {
+#if !USE_OS_TZDB
+        auto li = std::lower_bound(links.begin(), links.end(), tz_name,
+#if HAS_STRING_VIEW
+        [](const link& z, const std::string_view& nm)
+#else
+        [](const link& z, const std::string& nm)
+#endif
+        {
+            return z.name() < nm;
+        });
+        if (li != links.end() && li->name() == tz_name)
+        {
+            zi = std::lower_bound(zones.begin(), zones.end(), li->target(),
+                [](const time_zone& z, const std::string& nm)
+                {
+                    return z.name() < nm;
+                });
+            if (zi != zones.end() && zi->name() == li->target())
+                return &*zi;
+        }
+#endif  // !USE_OS_TZDB
+        throw std::runtime_error(std::string(tz_name) + " not found in timezone database");
+    }
+    return &*zi;
+}
+
+const time_zone*
+#if HAS_STRING_VIEW
+locate_zone(std::string_view tz_name)
+#else
+locate_zone(const std::string& tz_name)
+#endif
+{
+    return get_tzdb().locate_zone(tz_name);
+}
+
+#if USE_OS_TZDB
+
+std::ostream&
+operator<<(std::ostream& os, const tzdb& db)
+{
+    os << "Version: " << db.version << "\n\n";
+    for (const auto& x : db.zones)
+        os << x << '\n';
+#if !MISSING_LEAP_SECONDS
+    os << '\n';
+    for (const auto& x : db.leaps)
+        os << x << '\n';
+#endif  // !MISSING_LEAP_SECONDS
+    return os;
+}
+
+#else  // !USE_OS_TZDB
+
+std::ostream&
+operator<<(std::ostream& os, const tzdb& db)
+{
+    os << "Version: " << db.version << '\n';
+    std::string title("--------------------------------------------"
+                      "--------------------------------------------\n"
+                      "Name           ""Start Y ""End Y   "
+                      "Beginning                              ""Offset  "
+                      "Designator\n"
+                      "--------------------------------------------"
+                      "--------------------------------------------\n");
+    int count = 0;
+    for (const auto& x : db.rules)
+    {
+        if (count++ % 50 == 0)
+            os << title;
+        os << x << '\n';
+    }
+    os << '\n';
+    title = std::string("---------------------------------------------------------"
+                        "--------------------------------------------------------\n"
+                        "Name                               ""Offset      "
+                        "Rule           ""Abrev      ""Until\n"
+                        "---------------------------------------------------------"
+                        "--------------------------------------------------------\n");
+    count = 0;
+    for (const auto& x : db.zones)
+    {
+        if (count++ % 10 == 0)
+            os << title;
+        os << x << '\n';
+    }
+    os << '\n';
+    title = std::string("---------------------------------------------------------"
+                        "--------------------------------------------------------\n"
+                        "Alias                                   ""To\n"
+                        "---------------------------------------------------------"
+                        "--------------------------------------------------------\n");
+    count = 0;
+    for (const auto& x : db.links)
+    {
+        if (count++ % 45 == 0)
+            os << title;
+        os << x << '\n';
+    }
+    os << '\n';
+    title = std::string("---------------------------------------------------------"
+                        "--------------------------------------------------------\n"
+                        "Leap second on\n"
+                        "---------------------------------------------------------"
+                        "--------------------------------------------------------\n");
+    os << title;
+    for (const auto& x : db.leaps)
+        os << x << '\n';
+    return os;
+}
+
+#endif  // !USE_OS_TZDB
+
+// -----------------------
+
+#ifdef _WIN32
+
+static
+std::string
+getTimeZoneKeyName()
+{
+    DYNAMIC_TIME_ZONE_INFORMATION dtzi{};
+    auto result = GetDynamicTimeZoneInformation(&dtzi);
+    if (result == TIME_ZONE_ID_INVALID)
+        throw std::runtime_error("current_zone(): GetDynamicTimeZoneInformation()"
+                                 " reported TIME_ZONE_ID_INVALID.");
+    auto wlen = wcslen(dtzi.TimeZoneKeyName);
+    char buf[128] = {};
+    assert(sizeof(buf) >= wlen+1);
+    wcstombs(buf, dtzi.TimeZoneKeyName, wlen);
+    if (strcmp(buf, "Coordinated Universal Time") == 0)
+        return "UTC";
+    return buf;
+}
+
+const time_zone*
+tzdb::current_zone() const
+{
+    std::string win_tzid = getTimeZoneKeyName();
+    std::string standard_tzid;
+    if (!native_to_standard_timezone_name(win_tzid, standard_tzid))
+    {
+        std::string msg;
+        msg = "current_zone() failed: A mapping from the Windows Time Zone id \"";
+        msg += win_tzid;
+        msg += "\" was not found in the time zone mapping database.";
+        throw std::runtime_error(msg);
+    }
+    return locate_zone(standard_tzid);
+}
+
+#else  // !_WIN32
+
+const time_zone*
+tzdb::current_zone() const
+{
+    // On some OS's a file called /etc/localtime may
+    // exist and it may be either a real file
+    // containing time zone details or a symlink to such a file.
+    // On MacOS and BSD Unix if this file is a symlink it
+    // might resolve to a path like this:
+    // "/usr/share/zoneinfo/America/Los_Angeles"
+    // If it does, we try to determine the current
+    // timezone from the remainder of the path by removing the prefix
+    // and hoping the rest resolves to a valid timezone.
+    // It may not always work though. If it doesn't then an
+    // exception will be thrown by local_timezone.
+    // The path may also take a relative form:
+    // "../usr/share/zoneinfo/America/Los_Angeles".
+    {
+        struct stat sb;
+        CONSTDATA auto timezone = "/etc/localtime";
+        if (lstat(timezone, &sb) == 0 && S_ISLNK(sb.st_mode) && sb.st_size > 0) {
+            using namespace std;
+            char rp[PATH_MAX+1] = {};
+            if (realpath(timezone, rp) == nullptr)
+                throw system_error(errno, system_category(), "realpath() failed");
+#if HAS_STRING_VIEW
+            string_view result = rp;
+            CONSTDATA string_view zoneinfo = "/zoneinfo/";
+            const size_t pos = result.rfind(zoneinfo);
+            if (pos == result.npos)
+                throw runtime_error(
+                    "current_zone() failed to find \"/zoneinfo/\" in " + string(result));
+            result.remove_prefix(pos + zoneinfo.size());
+#else
+            string result = rp;
+            CONSTDATA char zoneinfo[] = "/zoneinfo/";
+            const size_t pos = result.rfind(zoneinfo);
+            if (pos == result.npos)
+                throw runtime_error(
+                    "current_zone() failed to find \"/zoneinfo/\" in " + result);
+            result.erase(0, pos + sizeof(zoneinfo) - 1);
+#endif
+            return locate_zone(result);
+        }
+    }
+    // On embedded systems e.g. buildroot with uclibc the timezone is linked
+    // into /etc/TZ which is a symlink to path like this:
+    // "/usr/share/zoneinfo/uclibc/America/Los_Angeles"
+    // If it does, we try to determine the current
+    // timezone from the remainder of the path by removing the prefix
+    // and hoping the rest resolves to valid timezone.
+    // It may not always work though. If it doesn't then an
+    // exception will be thrown by local_timezone.
+    // The path may also take a relative form:
+    // "../usr/share/zoneinfo/uclibc/America/Los_Angeles".
+    {
+        struct stat sb;
+        CONSTDATA auto timezone = "/etc/TZ";
+        if (lstat(timezone, &sb) == 0 && S_ISLNK(sb.st_mode) && sb.st_size > 0) {
+            using namespace std;
+            string result;
+            char rp[PATH_MAX+1] = {};
+            if (readlink(timezone, rp, sizeof(rp)-1) > 0)
+                result = string(rp);
+            else
+                throw system_error(errno, system_category(), "readlink() failed");
+
+            const size_t pos = result.find(get_tz_dir());
+            if (pos != result.npos)
+                result.erase(0, get_tz_dir().size() + 1 + pos);
+            return locate_zone(result);
+        }
+    }
+    {
+    // On some versions of some linux distro's (e.g. Ubuntu),
+    // the current timezone might be in the first line of
+    // the /etc/timezone file.
+        std::ifstream timezone_file("/etc/timezone");
+        if (timezone_file.is_open())
+        {
+            std::string result;
+            std::getline(timezone_file, result);
+            if (!result.empty())
+                return locate_zone(result);
+        }
+        // Fall through to try other means.
+    }
+    {
+    // On some versions of some bsd distro's (e.g. FreeBSD),
+    // the current timezone might be in the first line of
+    // the /var/db/zoneinfo file.
+        std::ifstream timezone_file("/var/db/zoneinfo");
+        if (timezone_file.is_open())
+        {
+            std::string result;
+            std::getline(timezone_file, result);
+            if (!result.empty())
+                return locate_zone(result);
+        }
+        // Fall through to try other means.
+    }
+    {
+    // On some versions of some bsd distro's (e.g. iOS),
+    // it is not possible to use file based approach,
+    // we switch to system API, calling functions in
+    // CoreFoundation framework.
+#if TARGET_OS_IPHONE
+        std::string result = date::iOSUtils::get_current_timezone();
+        if (!result.empty())
+            return locate_zone(result);
+#endif
+    // Fall through to try other means.
+    }
+    {
+    // On some versions of some linux distro's (e.g. Red Hat),
+    // the current timezone might be in the first line of
+    // the /etc/sysconfig/clock file as:
+    // ZONE="US/Eastern"
+        std::ifstream timezone_file("/etc/sysconfig/clock");
+        std::string result;
+        while (timezone_file)
+        {
+            std::getline(timezone_file, result);
+            auto p = result.find("ZONE=\"");
+            if (p != std::string::npos)
+            {
+                result.erase(p, p+6);
+                result.erase(result.rfind('"'));
+                return locate_zone(result);
+            }
+        }
+        // Fall through to try other means.
+    }
+    throw std::runtime_error("Could not get current timezone");
+}
+
+#endif  // !_WIN32
+
+const time_zone*
+current_zone()
+{
+    return get_tzdb().current_zone();
+}
+
+}  // namespace date
+
+#if defined(__GNUC__) && __GNUC__ < 5
+# pragma GCC diagnostic pop
+#endif