Mercurial > thymian
view 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 source
// 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
