http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/8777ab5f/thirdparty/date/src/tz.cpp ---------------------------------------------------------------------- diff --git a/thirdparty/date/src/tz.cpp b/thirdparty/date/src/tz.cpp new file mode 100644 index 0000000..f4a819b --- /dev/null +++ b/thirdparty/date/src/tz.cpp @@ -0,0 +1,3788 @@ +// 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 <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 +# 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 + +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); + folder = std::string(folder_ptr.get(), folder_ptr.get() + wcslen(folder_ptr.get())); + } + return folder; +} + +// Usually something like "c:\Users\username\Downloads". +static +std::string +get_download_folder() +{ + return get_known_folder(FOLDERID_Downloads); +} + +# else // !_WIN32 + +# if !defined(INSTALL) || HAS_REMOTE_API + +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) || HAS_REMOTE_API + +# 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::jan/1; +CONSTDATA auto max_day = date::dec/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 | +// +-------------------+ + +namespace detail +{ +struct undocumented {explicit undocumented() = default;}; +} + +#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: +// http://unicode.org/repos/cldr/trunk/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_stream<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(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*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_stream<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, "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_stream<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_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 { +
<TRUNCATED> http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/8777ab5f/thirdparty/date/test_fail.sh ---------------------------------------------------------------------- diff --git a/thirdparty/date/test_fail.sh b/thirdparty/date/test_fail.sh new file mode 100755 index 0000000..6562647 --- /dev/null +++ b/thirdparty/date/test_fail.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +echo $1 +eval $1 + +if [ $? -eq 0 ]; then + exit 0; +fi +exit 1; +
