ferencd@0: // The MIT License (MIT) ferencd@0: // ferencd@0: // Copyright (c) 2015, 2016, 2017 Howard Hinnant ferencd@0: // Copyright (c) 2015 Ville Voutilainen ferencd@0: // Copyright (c) 2016 Alexander Kormanovsky ferencd@0: // Copyright (c) 2016, 2017 Jiangang Zhuang ferencd@0: // Copyright (c) 2017 Nicolas Veloz Savino ferencd@0: // Copyright (c) 2017 Florian Dang ferencd@0: // Copyright (c) 2017 Aaron Bishop ferencd@0: // ferencd@0: // Permission is hereby granted, free of charge, to any person obtaining a copy ferencd@0: // of this software and associated documentation files (the "Software"), to deal ferencd@0: // in the Software without restriction, including without limitation the rights ferencd@0: // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ferencd@0: // copies of the Software, and to permit persons to whom the Software is ferencd@0: // furnished to do so, subject to the following conditions: ferencd@0: // ferencd@0: // The above copyright notice and this permission notice shall be included in all ferencd@0: // copies or substantial portions of the Software. ferencd@0: // ferencd@0: // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ferencd@0: // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ferencd@0: // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ferencd@0: // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ferencd@0: // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ferencd@0: // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE ferencd@0: // SOFTWARE. ferencd@0: // ferencd@0: // Our apologies. When the previous paragraph was written, lowercase had not yet ferencd@0: // been invented (that would involve another several millennia of evolution). ferencd@0: // We did not mean to shout. ferencd@0: ferencd@0: #ifdef _WIN32 ferencd@0: // windows.h will be included directly and indirectly (e.g. by curl). ferencd@0: // We need to define these macros to prevent windows.h bringing in ferencd@0: // more than we need and do it early so windows.h doesn't get included ferencd@0: // without these macros having been defined. ferencd@0: // min/max macros interfere with the C++ versions. ferencd@0: # ifndef NOMINMAX ferencd@0: # define NOMINMAX ferencd@0: # endif ferencd@0: // We don't need all that Windows has to offer. ferencd@0: # ifndef WIN32_LEAN_AND_MEAN ferencd@0: # define WIN32_LEAN_AND_MEAN ferencd@0: # endif ferencd@0: ferencd@0: // for wcstombs ferencd@0: # ifndef _CRT_SECURE_NO_WARNINGS ferencd@0: # define _CRT_SECURE_NO_WARNINGS ferencd@0: # endif ferencd@0: ferencd@0: // None of this happens with the MS SDK (at least VS14 which I tested), but: ferencd@0: // Compiling with mingw, we get "error: 'KF_FLAG_DEFAULT' was not declared in this scope." ferencd@0: // and error: 'SHGetKnownFolderPath' was not declared in this scope.". ferencd@0: // It seems when using mingw NTDDI_VERSION is undefined and that ferencd@0: // causes KNOWN_FOLDER_FLAG and the KF_ flags to not get defined. ferencd@0: // So we must define NTDDI_VERSION to get those flags on mingw. ferencd@0: // The docs say though here: ferencd@0: // https://msdn.microsoft.com/en-nz/library/windows/desktop/aa383745(v=vs.85).aspx ferencd@0: // that "If you define NTDDI_VERSION, you must also define _WIN32_WINNT." ferencd@0: // So we declare we require Vista or greater. ferencd@0: # ifdef __MINGW32__ ferencd@0: ferencd@0: # ifndef NTDDI_VERSION ferencd@0: # define NTDDI_VERSION 0x06000000 ferencd@0: # define _WIN32_WINNT _WIN32_WINNT_VISTA ferencd@0: # elif NTDDI_VERSION < 0x06000000 ferencd@0: # warning "If this fails to compile NTDDI_VERSION may be to low. See comments above." ferencd@0: # endif ferencd@0: // But once we define the values above we then get this linker error: ferencd@0: // "tz.cpp:(.rdata$.refptr.FOLDERID_Downloads[.refptr.FOLDERID_Downloads]+0x0): " ferencd@0: // "undefined reference to `FOLDERID_Downloads'" ferencd@0: // which #include cures see: ferencd@0: // https://support.microsoft.com/en-us/kb/130869 ferencd@0: # include ferencd@0: // But with included, the error moves on to: ferencd@0: // error: 'FOLDERID_Downloads' was not declared in this scope ferencd@0: // Which #include cures. ferencd@0: # include ferencd@0: ferencd@0: # endif // __MINGW32__ ferencd@0: ferencd@0: # include ferencd@0: #endif // _WIN32 ferencd@0: ferencd@0: #include "date/tz_private.h" ferencd@0: ferencd@0: #ifdef __APPLE__ ferencd@0: # include "date/ios.h" ferencd@0: #else ferencd@0: # define TARGET_OS_IPHONE 0 ferencd@0: #endif ferencd@0: ferencd@0: #if USE_OS_TZDB ferencd@0: # include ferencd@0: #endif ferencd@0: #include ferencd@0: #include ferencd@0: #include ferencd@0: #include ferencd@0: #include ferencd@0: #include ferencd@0: #include ferencd@0: #include ferencd@0: #include ferencd@0: #include ferencd@0: #if USE_OS_TZDB ferencd@0: # include ferencd@0: #endif ferencd@0: #include ferencd@0: #include ferencd@0: #include ferencd@0: #include ferencd@0: #include ferencd@0: ferencd@0: // unistd.h is used on some platforms as part of the the means to get ferencd@0: // the current time zone. On Win32 windows.h provides a means to do it. ferencd@0: // gcc/mingw supports unistd.h on Win32 but MSVC does not. ferencd@0: ferencd@0: #ifdef _WIN32 ferencd@0: # ifdef WINAPI_FAMILY ferencd@0: # include ferencd@0: # if WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP ferencd@0: # define WINRT ferencd@0: # define INSTALL . ferencd@0: # endif ferencd@0: # endif ferencd@0: ferencd@0: # include // _unlink etc. ferencd@0: ferencd@0: # if defined(__clang__) ferencd@0: struct IUnknown; // fix for issue with static_cast<> in objbase.h ferencd@0: // (see https://github.com/philsquared/Catch/issues/690) ferencd@0: # endif ferencd@0: ferencd@0: # include // CoTaskFree, ShGetKnownFolderPath etc. ferencd@0: # if HAS_REMOTE_API ferencd@0: # include // _mkdir ferencd@0: # include // ShFileOperation etc. ferencd@0: # endif // HAS_REMOTE_API ferencd@0: #else // !_WIN32 ferencd@0: # include ferencd@0: # if !USE_OS_TZDB ferencd@0: # include ferencd@0: # endif ferencd@0: # include ferencd@0: # include ferencd@0: # if !USE_SHELL_API ferencd@0: # include ferencd@0: # include ferencd@0: # include ferencd@0: # include ferencd@0: # include ferencd@0: # include ferencd@0: # endif //!USE_SHELL_API ferencd@0: #endif // !_WIN32 ferencd@0: ferencd@0: ferencd@0: #if HAS_REMOTE_API ferencd@0: // Note curl includes windows.h so we must include curl AFTER definitions of things ferencd@0: // that affect windows.h such as NOMINMAX. ferencd@0: #if defined(_MSC_VER) && defined(SHORTENED_CURL_INCLUDE) ferencd@0: // For rmt_curl nuget package ferencd@0: # include ferencd@0: #else ferencd@0: # include ferencd@0: #endif ferencd@0: #endif ferencd@0: ferencd@0: #ifdef _WIN32 ferencd@0: static CONSTDATA char folder_delimiter = '\\'; ferencd@0: #else // !_WIN32 ferencd@0: static CONSTDATA char folder_delimiter = '/'; ferencd@0: #endif // !_WIN32 ferencd@0: ferencd@0: #if defined(__GNUC__) && __GNUC__ < 5 ferencd@0: // GCC 4.9 Bug 61489 Wrong warning with -Wmissing-field-initializers ferencd@0: # pragma GCC diagnostic push ferencd@0: # pragma GCC diagnostic ignored "-Wmissing-field-initializers" ferencd@0: #endif // defined(__GNUC__) && __GNUC__ < 5 ferencd@0: ferencd@0: #if !USE_OS_TZDB ferencd@0: ferencd@0: # ifdef _WIN32 ferencd@0: # ifndef WINRT ferencd@0: ferencd@0: namespace ferencd@0: { ferencd@0: struct task_mem_deleter ferencd@0: { ferencd@0: void operator()(wchar_t buf[]) ferencd@0: { ferencd@0: if (buf != nullptr) ferencd@0: CoTaskMemFree(buf); ferencd@0: } ferencd@0: }; ferencd@0: using co_task_mem_ptr = std::unique_ptr; ferencd@0: } ferencd@0: ferencd@0: // We might need to know certain locations even if not using the remote API, ferencd@0: // so keep these routines out of that block for now. ferencd@0: static ferencd@0: std::string ferencd@0: get_known_folder(const GUID& folderid) ferencd@0: { ferencd@0: std::string folder; ferencd@0: PWSTR pfolder = nullptr; ferencd@0: HRESULT hr = SHGetKnownFolderPath(folderid, KF_FLAG_DEFAULT, nullptr, &pfolder); ferencd@0: if (SUCCEEDED(hr)) ferencd@0: { ferencd@0: co_task_mem_ptr folder_ptr(pfolder); ferencd@0: const wchar_t* fptr = folder_ptr.get(); ferencd@0: auto state = std::mbstate_t(); ferencd@0: const auto required = std::wcsrtombs(nullptr, &fptr, 0, &state); ferencd@0: if (required != 0 && required != std::size_t(-1)) ferencd@0: { ferencd@0: folder.resize(required); ferencd@0: std::wcsrtombs(&folder[0], &fptr, folder.size(), &state); ferencd@0: } ferencd@0: } ferencd@0: return folder; ferencd@0: } ferencd@0: ferencd@0: # ifndef INSTALL ferencd@0: ferencd@0: // Usually something like "c:\Users\username\Downloads". ferencd@0: static ferencd@0: std::string ferencd@0: get_download_folder() ferencd@0: { ferencd@0: return get_known_folder(FOLDERID_Downloads); ferencd@0: } ferencd@0: ferencd@0: # endif // !INSTALL ferencd@0: ferencd@0: # endif // WINRT ferencd@0: # else // !_WIN32 ferencd@0: ferencd@0: # if !defined(INSTALL) ferencd@0: ferencd@0: static ferencd@0: std::string ferencd@0: expand_path(std::string path) ferencd@0: { ferencd@0: # if TARGET_OS_IPHONE ferencd@0: return date::iOSUtils::get_tzdata_path(); ferencd@0: # else // !TARGET_OS_IPHONE ferencd@0: ::wordexp_t w{}; ferencd@0: std::unique_ptr<::wordexp_t, void(*)(::wordexp_t*)> hold{&w, ::wordfree}; ferencd@0: ::wordexp(path.c_str(), &w, 0); ferencd@0: if (w.we_wordc != 1) ferencd@0: throw std::runtime_error("Cannot expand path: " + path); ferencd@0: path = w.we_wordv[0]; ferencd@0: return path; ferencd@0: # endif // !TARGET_OS_IPHONE ferencd@0: } ferencd@0: ferencd@0: static ferencd@0: std::string ferencd@0: get_download_folder() ferencd@0: { ferencd@0: return expand_path("~/Downloads"); ferencd@0: } ferencd@0: ferencd@0: # endif // !defined(INSTALL) ferencd@0: ferencd@0: # endif // !_WIN32 ferencd@0: ferencd@0: #endif // !USE_OS_TZDB ferencd@0: ferencd@0: namespace date ferencd@0: { ferencd@0: // +---------------------+ ferencd@0: // | Begin Configuration | ferencd@0: // +---------------------+ ferencd@0: ferencd@0: using namespace detail; ferencd@0: ferencd@0: #if !USE_OS_TZDB ferencd@0: ferencd@0: static ferencd@0: std::string& ferencd@0: access_install() ferencd@0: { ferencd@0: static std::string install ferencd@0: #ifndef INSTALL ferencd@0: ferencd@0: = get_download_folder() + folder_delimiter + "tzdata"; ferencd@0: ferencd@0: #else // !INSTALL ferencd@0: ferencd@0: # define STRINGIZEIMP(x) #x ferencd@0: # define STRINGIZE(x) STRINGIZEIMP(x) ferencd@0: ferencd@0: = STRINGIZE(INSTALL) + std::string(1, folder_delimiter) + "tzdata"; ferencd@0: ferencd@0: #undef STRINGIZEIMP ferencd@0: #undef STRINGIZE ferencd@0: #endif // !INSTALL ferencd@0: ferencd@0: return install; ferencd@0: } ferencd@0: ferencd@0: void ferencd@0: set_install(const std::string& s) ferencd@0: { ferencd@0: access_install() = s; ferencd@0: } ferencd@0: ferencd@0: static ferencd@0: const std::string& ferencd@0: get_install() ferencd@0: { ferencd@0: static const std::string& ref = access_install(); ferencd@0: return ref; ferencd@0: } ferencd@0: ferencd@0: #if HAS_REMOTE_API ferencd@0: static ferencd@0: std::string ferencd@0: get_download_gz_file(const std::string& version) ferencd@0: { ferencd@0: auto file = get_install() + version + ".tar.gz"; ferencd@0: return file; ferencd@0: } ferencd@0: #endif // HAS_REMOTE_API ferencd@0: ferencd@0: #endif // !USE_OS_TZDB ferencd@0: ferencd@0: // These can be used to reduce the range of the database to save memory ferencd@0: CONSTDATA auto min_year = date::year::min(); ferencd@0: CONSTDATA auto max_year = date::year::max(); ferencd@0: ferencd@0: CONSTDATA auto min_day = date::January/1; ferencd@0: CONSTDATA auto max_day = date::December/31; ferencd@0: ferencd@0: #if USE_OS_TZDB ferencd@0: ferencd@0: CONSTCD14 const sys_seconds min_seconds = sys_days(min_year/min_day); ferencd@0: ferencd@0: #endif // USE_OS_TZDB ferencd@0: ferencd@0: #ifndef _WIN32 ferencd@0: ferencd@0: static ferencd@0: std::string ferencd@0: discover_tz_dir() ferencd@0: { ferencd@0: struct stat sb; ferencd@0: using namespace std; ferencd@0: # ifndef __APPLE__ ferencd@0: CONSTDATA auto tz_dir_default = "/usr/share/zoneinfo"; ferencd@0: CONSTDATA auto tz_dir_buildroot = "/usr/share/zoneinfo/uclibc"; ferencd@0: ferencd@0: // Check special path which is valid for buildroot with uclibc builds ferencd@0: if(stat(tz_dir_buildroot, &sb) == 0 && S_ISDIR(sb.st_mode)) ferencd@0: return tz_dir_buildroot; ferencd@0: else if(stat(tz_dir_default, &sb) == 0 && S_ISDIR(sb.st_mode)) ferencd@0: return tz_dir_default; ferencd@0: else ferencd@0: throw runtime_error("discover_tz_dir failed to find zoneinfo\n"); ferencd@0: # else // __APPLE__ ferencd@0: # if TARGET_OS_IPHONE ferencd@0: return "/var/db/timezone/zoneinfo"; ferencd@0: # else ferencd@0: CONSTDATA auto timezone = "/etc/localtime"; ferencd@0: if (!(lstat(timezone, &sb) == 0 && S_ISLNK(sb.st_mode) && sb.st_size > 0)) ferencd@0: throw runtime_error("discover_tz_dir failed\n"); ferencd@0: string result; ferencd@0: char rp[PATH_MAX+1] = {}; ferencd@0: if (readlink(timezone, rp, sizeof(rp)-1) > 0) ferencd@0: result = string(rp); ferencd@0: else ferencd@0: throw system_error(errno, system_category(), "readlink() failed"); ferencd@0: auto i = result.find("zoneinfo"); ferencd@0: if (i == string::npos) ferencd@0: throw runtime_error("discover_tz_dir failed to find zoneinfo\n"); ferencd@0: i = result.find('/', i); ferencd@0: if (i == string::npos) ferencd@0: throw runtime_error("discover_tz_dir failed to find '/'\n"); ferencd@0: return result.substr(0, i); ferencd@0: # endif ferencd@0: # endif // __APPLE__ ferencd@0: } ferencd@0: ferencd@0: static ferencd@0: const std::string& ferencd@0: get_tz_dir() ferencd@0: { ferencd@0: static const std::string tz_dir = discover_tz_dir(); ferencd@0: return tz_dir; ferencd@0: } ferencd@0: ferencd@0: #endif ferencd@0: ferencd@0: // +-------------------+ ferencd@0: // | End Configuration | ferencd@0: // +-------------------+ ferencd@0: ferencd@0: #ifndef _MSC_VER ferencd@0: static_assert(min_year <= max_year, "Configuration error"); ferencd@0: #endif ferencd@0: ferencd@0: static std::unique_ptr init_tzdb(); ferencd@0: ferencd@0: tzdb_list::~tzdb_list() ferencd@0: { ferencd@0: const tzdb* ptr = head_; ferencd@0: head_ = nullptr; ferencd@0: while (ptr != nullptr) ferencd@0: { ferencd@0: auto next = ptr->next; ferencd@0: delete ptr; ferencd@0: ptr = next; ferencd@0: } ferencd@0: } ferencd@0: ferencd@0: tzdb_list::tzdb_list(tzdb_list&& x) noexcept ferencd@0: : head_{x.head_.exchange(nullptr)} ferencd@0: { ferencd@0: } ferencd@0: ferencd@0: void ferencd@0: tzdb_list::push_front(tzdb* tzdb) noexcept ferencd@0: { ferencd@0: tzdb->next = head_; ferencd@0: head_ = tzdb; ferencd@0: } ferencd@0: ferencd@0: tzdb_list::const_iterator ferencd@0: tzdb_list::erase_after(const_iterator p) noexcept ferencd@0: { ferencd@0: auto t = p.p_->next; ferencd@0: p.p_->next = p.p_->next->next; ferencd@0: delete t; ferencd@0: return ++p; ferencd@0: } ferencd@0: ferencd@0: struct tzdb_list::undocumented_helper ferencd@0: { ferencd@0: static void push_front(tzdb_list& db_list, tzdb* tzdb) noexcept ferencd@0: { ferencd@0: db_list.push_front(tzdb); ferencd@0: } ferencd@0: }; ferencd@0: ferencd@0: static ferencd@0: tzdb_list ferencd@0: create_tzdb() ferencd@0: { ferencd@0: tzdb_list tz_db; ferencd@0: tzdb_list::undocumented_helper::push_front(tz_db, init_tzdb().release()); ferencd@0: return tz_db; ferencd@0: } ferencd@0: ferencd@0: tzdb_list& ferencd@0: get_tzdb_list() ferencd@0: { ferencd@0: static tzdb_list tz_db = create_tzdb(); ferencd@0: return tz_db; ferencd@0: } ferencd@0: ferencd@0: #if !USE_OS_TZDB ferencd@0: ferencd@0: #ifdef _WIN32 ferencd@0: ferencd@0: static ferencd@0: void ferencd@0: sort_zone_mappings(std::vector& mappings) ferencd@0: { ferencd@0: std::sort(mappings.begin(), mappings.end(), ferencd@0: [](const date::detail::timezone_mapping& lhs, ferencd@0: const date::detail::timezone_mapping& rhs)->bool ferencd@0: { ferencd@0: auto other_result = lhs.other.compare(rhs.other); ferencd@0: if (other_result < 0) ferencd@0: return true; ferencd@0: else if (other_result == 0) ferencd@0: { ferencd@0: auto territory_result = lhs.territory.compare(rhs.territory); ferencd@0: if (territory_result < 0) ferencd@0: return true; ferencd@0: else if (territory_result == 0) ferencd@0: { ferencd@0: if (lhs.type < rhs.type) ferencd@0: return true; ferencd@0: } ferencd@0: } ferencd@0: return false; ferencd@0: }); ferencd@0: } ferencd@0: ferencd@0: static ferencd@0: bool ferencd@0: native_to_standard_timezone_name(const std::string& native_tz_name, ferencd@0: std::string& standard_tz_name) ferencd@0: { ferencd@0: // TOOD! Need be a case insensitive compare? ferencd@0: if (native_tz_name == "UTC") ferencd@0: { ferencd@0: standard_tz_name = "Etc/UTC"; ferencd@0: return true; ferencd@0: } ferencd@0: standard_tz_name.clear(); ferencd@0: // TODO! we can improve on linear search. ferencd@0: const auto& mappings = date::get_tzdb().mappings; ferencd@0: for (const auto& tzm : mappings) ferencd@0: { ferencd@0: if (tzm.other == native_tz_name) ferencd@0: { ferencd@0: standard_tz_name = tzm.type; ferencd@0: return true; ferencd@0: } ferencd@0: } ferencd@0: return false; ferencd@0: } ferencd@0: ferencd@0: // Parse this XML file: ferencd@0: // https://raw.githubusercontent.com/unicode-org/cldr/master/common/supplemental/windowsZones.xml ferencd@0: // The parsing method is designed to be simple and quick. It is not overly ferencd@0: // forgiving of change but it should diagnose basic format issues. ferencd@0: // See timezone_mapping structure for more info. ferencd@0: static ferencd@0: std::vector ferencd@0: load_timezone_mappings_from_xml_file(const std::string& input_path) ferencd@0: { ferencd@0: std::size_t line_num = 0; ferencd@0: std::vector mappings; ferencd@0: std::string line; ferencd@0: ferencd@0: std::ifstream is(input_path); ferencd@0: if (!is.is_open()) ferencd@0: { ferencd@0: // We don't emit file exceptions because that's an implementation detail. ferencd@0: std::string msg = "Error opening time zone mapping file \""; ferencd@0: msg += input_path; ferencd@0: msg += "\"."; ferencd@0: throw std::runtime_error(msg); ferencd@0: } ferencd@0: ferencd@0: auto error = [&input_path, &line_num](const char* info) ferencd@0: { ferencd@0: std::string msg = "Error loading time zone mapping file \""; ferencd@0: msg += input_path; ferencd@0: msg += "\" at line "; ferencd@0: msg += std::to_string(line_num); ferencd@0: msg += ": "; ferencd@0: msg += info; ferencd@0: throw std::runtime_error(msg); ferencd@0: }; ferencd@0: // [optional space]a="b" ferencd@0: auto read_attribute = [&line, &error] ferencd@0: (const char* name, std::string& value, std::size_t startPos) ferencd@0: ->std::size_t ferencd@0: { ferencd@0: value.clear(); ferencd@0: // Skip leading space before attribute name. ferencd@0: std::size_t spos = line.find_first_not_of(' ', startPos); ferencd@0: if (spos == std::string::npos) ferencd@0: spos = startPos; ferencd@0: // Assume everything up to next = is the attribute name ferencd@0: // and that an = will always delimit that. ferencd@0: std::size_t epos = line.find('=', spos); ferencd@0: if (epos == std::string::npos) ferencd@0: error("Expected \'=\' right after attribute name."); ferencd@0: std::size_t name_len = epos - spos; ferencd@0: // Expect the name we find matches the name we expect. ferencd@0: if (line.compare(spos, name_len, name) != 0) ferencd@0: { ferencd@0: std::string msg; ferencd@0: msg = "Expected attribute name \'"; ferencd@0: msg += name; ferencd@0: msg += "\' around position "; ferencd@0: msg += std::to_string(spos); ferencd@0: msg += " but found something else."; ferencd@0: error(msg.c_str()); ferencd@0: } ferencd@0: ++epos; // Skip the '=' that is after the attribute name. ferencd@0: spos = epos; ferencd@0: if (spos < line.length() && line[spos] == '\"') ferencd@0: ++spos; // Skip the quote that is before the attribute value. ferencd@0: else ferencd@0: { ferencd@0: std::string msg = "Expected '\"' to begin value of attribute \'"; ferencd@0: msg += name; ferencd@0: msg += "\'."; ferencd@0: error(msg.c_str()); ferencd@0: } ferencd@0: epos = line.find('\"', spos); ferencd@0: if (epos == std::string::npos) ferencd@0: { ferencd@0: std::string msg = "Expected '\"' to end value of attribute \'"; ferencd@0: msg += name; ferencd@0: msg += "\'."; ferencd@0: error(msg.c_str()); ferencd@0: } ferencd@0: // Extract everything in between the quotes. Note no escaping is done. ferencd@0: std::size_t value_len = epos - spos; ferencd@0: value.assign(line, spos, value_len); ferencd@0: ++epos; // Skip the quote that is after the attribute value; ferencd@0: return epos; ferencd@0: }; ferencd@0: ferencd@0: // Quick but not overly forgiving XML mapping file processing. ferencd@0: bool mapTimezonesOpenTagFound = false; ferencd@0: bool mapTimezonesCloseTagFound = false; ferencd@0: std::size_t mapZonePos = std::string::npos; ferencd@0: std::size_t mapTimezonesPos = std::string::npos; ferencd@0: CONSTDATA char mapTimeZonesOpeningTag[] = { ""); ferencd@0: mapTimezonesCloseTagFound = (mapTimezonesPos != std::string::npos); ferencd@0: if (!mapTimezonesCloseTagFound) ferencd@0: { ferencd@0: std::size_t commentPos = line.find(" " << x.target_; ferencd@0: } ferencd@0: ferencd@0: // leap ferencd@0: ferencd@0: leap::leap(const std::string& s, detail::undocumented) ferencd@0: { ferencd@0: using namespace date; ferencd@0: std::istringstream in(s); ferencd@0: in.exceptions(std::ios::failbit | std::ios::badbit); ferencd@0: std::string word; ferencd@0: int y; ferencd@0: MonthDayTime date; ferencd@0: in >> word >> y >> date; ferencd@0: date_ = date.to_time_point(year(y)); ferencd@0: } ferencd@0: ferencd@0: static ferencd@0: bool ferencd@0: file_exists(const std::string& filename) ferencd@0: { ferencd@0: #ifdef _WIN32 ferencd@0: return ::_access(filename.c_str(), 0) == 0; ferencd@0: #else ferencd@0: return ::access(filename.c_str(), F_OK) == 0; ferencd@0: #endif ferencd@0: } ferencd@0: ferencd@0: #if HAS_REMOTE_API ferencd@0: ferencd@0: // CURL tools ferencd@0: ferencd@0: static ferencd@0: int ferencd@0: curl_global() ferencd@0: { ferencd@0: if (::curl_global_init(CURL_GLOBAL_DEFAULT) != 0) ferencd@0: throw std::runtime_error("CURL global initialization failed"); ferencd@0: return 0; ferencd@0: } ferencd@0: ferencd@0: namespace ferencd@0: { ferencd@0: ferencd@0: struct curl_deleter ferencd@0: { ferencd@0: void operator()(CURL* p) const ferencd@0: { ferencd@0: ::curl_easy_cleanup(p); ferencd@0: } ferencd@0: }; ferencd@0: ferencd@0: } // unnamed namespace ferencd@0: ferencd@0: static ferencd@0: std::unique_ptr ferencd@0: curl_init() ferencd@0: { ferencd@0: static const auto curl_is_now_initiailized = curl_global(); ferencd@0: (void)curl_is_now_initiailized; ferencd@0: return std::unique_ptr{::curl_easy_init()}; ferencd@0: } ferencd@0: ferencd@0: static ferencd@0: bool ferencd@0: download_to_string(const std::string& url, std::string& str) ferencd@0: { ferencd@0: str.clear(); ferencd@0: auto curl = curl_init(); ferencd@0: if (!curl) ferencd@0: return false; ferencd@0: std::string version; ferencd@0: curl_easy_setopt(curl.get(), CURLOPT_USERAGENT, "curl"); ferencd@0: curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str()); ferencd@0: curl_write_callback write_cb = [](char* contents, std::size_t size, std::size_t nmemb, ferencd@0: void* userp) -> std::size_t ferencd@0: { ferencd@0: auto& userstr = *static_cast(userp); ferencd@0: auto realsize = size * nmemb; ferencd@0: userstr.append(contents, realsize); ferencd@0: return realsize; ferencd@0: }; ferencd@0: curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, write_cb); ferencd@0: curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &str); ferencd@0: curl_easy_setopt(curl.get(), CURLOPT_SSL_VERIFYPEER, false); ferencd@0: auto res = curl_easy_perform(curl.get()); ferencd@0: return (res == CURLE_OK); ferencd@0: } ferencd@0: ferencd@0: namespace ferencd@0: { ferencd@0: enum class download_file_options { binary, text }; ferencd@0: } ferencd@0: ferencd@0: static ferencd@0: bool ferencd@0: download_to_file(const std::string& url, const std::string& local_filename, ferencd@0: download_file_options opts) ferencd@0: { ferencd@0: auto curl = curl_init(); ferencd@0: if (!curl) ferencd@0: return false; ferencd@0: curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str()); ferencd@0: curl_easy_setopt(curl.get(), CURLOPT_SSL_VERIFYPEER, false); ferencd@0: curl_write_callback write_cb = [](char* contents, std::size_t size, std::size_t nmemb, ferencd@0: void* userp) -> std::size_t ferencd@0: { ferencd@0: auto& of = *static_cast(userp); ferencd@0: auto realsize = size * nmemb; ferencd@0: of.write(contents, static_cast(realsize)); ferencd@0: return realsize; ferencd@0: }; ferencd@0: curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, write_cb); ferencd@0: decltype(curl_easy_perform(curl.get())) res; ferencd@0: { ferencd@0: std::ofstream of(local_filename, ferencd@0: opts == download_file_options::binary ? ferencd@0: std::ofstream::out | std::ofstream::binary : ferencd@0: std::ofstream::out); ferencd@0: of.exceptions(std::ios::badbit); ferencd@0: curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &of); ferencd@0: res = curl_easy_perform(curl.get()); ferencd@0: } ferencd@0: return res == CURLE_OK; ferencd@0: } ferencd@0: ferencd@0: std::string ferencd@0: remote_version() ferencd@0: { ferencd@0: std::string version; ferencd@0: std::string str; ferencd@0: if (download_to_string("https://www.iana.org/time-zones", str)) ferencd@0: { ferencd@0: CONSTDATA char db[] = "/time-zones/releases/tzdata"; ferencd@0: CONSTDATA auto db_size = sizeof(db) - 1; ferencd@0: auto p = str.find(db, 0, db_size); ferencd@0: const int ver_str_len = 5; ferencd@0: if (p != std::string::npos && p + (db_size + ver_str_len) <= str.size()) ferencd@0: version = str.substr(p + db_size, ver_str_len); ferencd@0: } ferencd@0: return version; ferencd@0: } ferencd@0: ferencd@0: ferencd@0: // TODO! Using system() create a process and a console window. ferencd@0: // This is useful to see what errors may occur but is slow and distracting. ferencd@0: // Consider implementing this functionality more directly, such as ferencd@0: // using _mkdir and CreateProcess etc. ferencd@0: // But use the current means now as matches Unix implementations and while ferencd@0: // in proof of concept / testing phase. ferencd@0: // TODO! Use eventually. ferencd@0: static ferencd@0: bool ferencd@0: remove_folder_and_subfolders(const std::string& folder) ferencd@0: { ferencd@0: # ifdef _WIN32 ferencd@0: # if USE_SHELL_API ferencd@0: // Delete the folder contents by deleting the folder. ferencd@0: std::string cmd = "rd /s /q \""; ferencd@0: cmd += folder; ferencd@0: cmd += '\"'; ferencd@0: return std::system(cmd.c_str()) == EXIT_SUCCESS; ferencd@0: # else // !USE_SHELL_API ferencd@0: // Create a buffer containing the path to delete. It must be terminated ferencd@0: // by two nuls. Who designs these API's... ferencd@0: std::vector from; ferencd@0: from.assign(folder.begin(), folder.end()); ferencd@0: from.push_back('\0'); ferencd@0: from.push_back('\0'); ferencd@0: SHFILEOPSTRUCT fo{}; // Zero initialize. ferencd@0: fo.wFunc = FO_DELETE; ferencd@0: fo.pFrom = from.data(); ferencd@0: fo.fFlags = FOF_NO_UI; ferencd@0: int ret = SHFileOperation(&fo); ferencd@0: if (ret == 0 && !fo.fAnyOperationsAborted) ferencd@0: return true; ferencd@0: return false; ferencd@0: # endif // !USE_SHELL_API ferencd@0: # else // !_WIN32 ferencd@0: # if USE_SHELL_API ferencd@0: return std::system(("rm -R " + folder).c_str()) == EXIT_SUCCESS; ferencd@0: # else // !USE_SHELL_API ferencd@0: struct dir_deleter { ferencd@0: dir_deleter() {} ferencd@0: void operator()(DIR* d) const ferencd@0: { ferencd@0: if (d != nullptr) ferencd@0: { ferencd@0: int result = closedir(d); ferencd@0: assert(result == 0); ferencd@0: } ferencd@0: } ferencd@0: }; ferencd@0: using closedir_ptr = std::unique_ptr; ferencd@0: ferencd@0: std::string filename; ferencd@0: struct stat statbuf; ferencd@0: std::size_t folder_len = folder.length(); ferencd@0: struct dirent* p = nullptr; ferencd@0: ferencd@0: closedir_ptr d(opendir(folder.c_str())); ferencd@0: bool r = d.get() != nullptr; ferencd@0: while (r && (p=readdir(d.get())) != nullptr) ferencd@0: { ferencd@0: if (strcmp(p->d_name, ".") == 0 || strcmp(p->d_name, "..") == 0) ferencd@0: continue; ferencd@0: ferencd@0: // + 2 for path delimiter and nul terminator. ferencd@0: std::size_t buf_len = folder_len + strlen(p->d_name) + 2; ferencd@0: filename.resize(buf_len); ferencd@0: std::size_t path_len = static_cast( ferencd@0: snprintf(&filename[0], buf_len, "%s/%s", folder.c_str(), p->d_name)); ferencd@0: assert(path_len == buf_len - 1); ferencd@0: filename.resize(path_len); ferencd@0: ferencd@0: if (stat(filename.c_str(), &statbuf) == 0) ferencd@0: r = S_ISDIR(statbuf.st_mode) ferencd@0: ? remove_folder_and_subfolders(filename) ferencd@0: : unlink(filename.c_str()) == 0; ferencd@0: } ferencd@0: d.reset(); ferencd@0: ferencd@0: if (r) ferencd@0: r = rmdir(folder.c_str()) == 0; ferencd@0: ferencd@0: return r; ferencd@0: # endif // !USE_SHELL_API ferencd@0: # endif // !_WIN32 ferencd@0: } ferencd@0: ferencd@0: static ferencd@0: bool ferencd@0: make_directory(const std::string& folder) ferencd@0: { ferencd@0: # ifdef _WIN32 ferencd@0: # if USE_SHELL_API ferencd@0: // Re-create the folder. ferencd@0: std::string cmd = "mkdir \""; ferencd@0: cmd += folder; ferencd@0: cmd += '\"'; ferencd@0: return std::system(cmd.c_str()) == EXIT_SUCCESS; ferencd@0: # else // !USE_SHELL_API ferencd@0: return _mkdir(folder.c_str()) == 0; ferencd@0: # endif // !USE_SHELL_API ferencd@0: # else // !_WIN32 ferencd@0: # if USE_SHELL_API ferencd@0: return std::system(("mkdir -p " + folder).c_str()) == EXIT_SUCCESS; ferencd@0: # else // !USE_SHELL_API ferencd@0: return mkdir(folder.c_str(), 0777) == 0; ferencd@0: # endif // !USE_SHELL_API ferencd@0: # endif // !_WIN32 ferencd@0: } ferencd@0: ferencd@0: static ferencd@0: bool ferencd@0: delete_file(const std::string& file) ferencd@0: { ferencd@0: # ifdef _WIN32 ferencd@0: # if USE_SHELL_API ferencd@0: std::string cmd = "del \""; ferencd@0: cmd += file; ferencd@0: cmd += '\"'; ferencd@0: return std::system(cmd.c_str()) == 0; ferencd@0: # else // !USE_SHELL_API ferencd@0: return _unlink(file.c_str()) == 0; ferencd@0: # endif // !USE_SHELL_API ferencd@0: # else // !_WIN32 ferencd@0: # if USE_SHELL_API ferencd@0: return std::system(("rm " + file).c_str()) == EXIT_SUCCESS; ferencd@0: # else // !USE_SHELL_API ferencd@0: return unlink(file.c_str()) == 0; ferencd@0: # endif // !USE_SHELL_API ferencd@0: # endif // !_WIN32 ferencd@0: } ferencd@0: ferencd@0: # ifdef _WIN32 ferencd@0: ferencd@0: static ferencd@0: bool ferencd@0: move_file(const std::string& from, const std::string& to) ferencd@0: { ferencd@0: # if USE_SHELL_API ferencd@0: std::string cmd = "move \""; ferencd@0: cmd += from; ferencd@0: cmd += "\" \""; ferencd@0: cmd += to; ferencd@0: cmd += '\"'; ferencd@0: return std::system(cmd.c_str()) == EXIT_SUCCESS; ferencd@0: # else // !USE_SHELL_API ferencd@0: return !!::MoveFile(from.c_str(), to.c_str()); ferencd@0: # endif // !USE_SHELL_API ferencd@0: } ferencd@0: ferencd@0: // Usually something like "c:\Program Files". ferencd@0: static ferencd@0: std::string ferencd@0: get_program_folder() ferencd@0: { ferencd@0: return get_known_folder(FOLDERID_ProgramFiles); ferencd@0: } ferencd@0: ferencd@0: // Note folder can and usually does contain spaces. ferencd@0: static ferencd@0: std::string ferencd@0: get_unzip_program() ferencd@0: { ferencd@0: std::string path; ferencd@0: ferencd@0: // 7-Zip appears to note its location in the registry. ferencd@0: // If that doesn't work, fall through and take a guess, but it will likely be wrong. ferencd@0: HKEY hKey = nullptr; ferencd@0: if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\7-Zip", 0, KEY_READ, &hKey) == ERROR_SUCCESS) ferencd@0: { ferencd@0: char value_buffer[MAX_PATH + 1]; // fyi 260 at time of writing. ferencd@0: // in/out parameter. Documentation say that size is a count of bytes not chars. ferencd@0: DWORD size = sizeof(value_buffer) - sizeof(value_buffer[0]); ferencd@0: DWORD tzi_type = REG_SZ; ferencd@0: // Testing shows Path key value is "C:\Program Files\7-Zip\" i.e. always with trailing \. ferencd@0: bool got_value = (RegQueryValueExA(hKey, "Path", nullptr, &tzi_type, ferencd@0: reinterpret_cast(value_buffer), &size) == ERROR_SUCCESS); ferencd@0: RegCloseKey(hKey); // Close now incase of throw later. ferencd@0: if (got_value) ferencd@0: { ferencd@0: // Function does not guarantee to null terminate. ferencd@0: value_buffer[size / sizeof(value_buffer[0])] = '\0'; ferencd@0: path = value_buffer; ferencd@0: if (!path.empty()) ferencd@0: { ferencd@0: path += "7z.exe"; ferencd@0: return path; ferencd@0: } ferencd@0: } ferencd@0: } ferencd@0: path += get_program_folder(); ferencd@0: path += folder_delimiter; ferencd@0: path += "7-Zip\\7z.exe"; ferencd@0: return path; ferencd@0: } ferencd@0: ferencd@0: # if !USE_SHELL_API ferencd@0: static ferencd@0: int ferencd@0: run_program(const std::string& command) ferencd@0: { ferencd@0: STARTUPINFO si{}; ferencd@0: si.cb = sizeof(si); ferencd@0: PROCESS_INFORMATION pi{}; ferencd@0: ferencd@0: // Allegedly CreateProcess overwrites the command line. Ugh. ferencd@0: std::string mutable_command(command); ferencd@0: if (CreateProcess(nullptr, &mutable_command[0], ferencd@0: nullptr, nullptr, FALSE, CREATE_NO_WINDOW, nullptr, nullptr, &si, &pi)) ferencd@0: { ferencd@0: WaitForSingleObject(pi.hProcess, INFINITE); ferencd@0: DWORD exit_code; ferencd@0: bool got_exit_code = !!GetExitCodeProcess(pi.hProcess, &exit_code); ferencd@0: CloseHandle(pi.hProcess); ferencd@0: CloseHandle(pi.hThread); ferencd@0: // Not 100% sure about this still active thing is correct, ferencd@0: // but I'm going with it because I *think* WaitForSingleObject might ferencd@0: // return in some cases without INFINITE-ly waiting. ferencd@0: // But why/wouldn't GetExitCodeProcess return false in that case? ferencd@0: if (got_exit_code && exit_code != STILL_ACTIVE) ferencd@0: return static_cast(exit_code); ferencd@0: } ferencd@0: return EXIT_FAILURE; ferencd@0: } ferencd@0: # endif // !USE_SHELL_API ferencd@0: ferencd@0: static ferencd@0: std::string ferencd@0: get_download_tar_file(const std::string& version) ferencd@0: { ferencd@0: auto file = get_install(); ferencd@0: file += folder_delimiter; ferencd@0: file += "tzdata"; ferencd@0: file += version; ferencd@0: file += ".tar"; ferencd@0: return file; ferencd@0: } ferencd@0: ferencd@0: static ferencd@0: bool ferencd@0: extract_gz_file(const std::string& version, const std::string& gz_file, ferencd@0: const std::string& dest_folder) ferencd@0: { ferencd@0: auto unzip_prog = get_unzip_program(); ferencd@0: bool unzip_result = false; ferencd@0: // Use the unzip program to extract the tar file from the archive. ferencd@0: ferencd@0: // Aim to create a string like: ferencd@0: // "C:\Program Files\7-Zip\7z.exe" x "C:\Users\SomeUser\Downloads\tzdata2016d.tar.gz" ferencd@0: // -o"C:\Users\SomeUser\Downloads\tzdata" ferencd@0: std::string cmd; ferencd@0: cmd = '\"'; ferencd@0: cmd += unzip_prog; ferencd@0: cmd += "\" x \""; ferencd@0: cmd += gz_file; ferencd@0: cmd += "\" -o\""; ferencd@0: cmd += dest_folder; ferencd@0: cmd += '\"'; ferencd@0: ferencd@0: # if USE_SHELL_API ferencd@0: // When using shelling out with std::system() extra quotes are required around the ferencd@0: // whole command. It's weird but necessary it seems, see: ferencd@0: // http://stackoverflow.com/q/27975969/576911 ferencd@0: ferencd@0: cmd = "\"" + cmd + "\""; ferencd@0: if (std::system(cmd.c_str()) == EXIT_SUCCESS) ferencd@0: unzip_result = true; ferencd@0: # else // !USE_SHELL_API ferencd@0: if (run_program(cmd) == EXIT_SUCCESS) ferencd@0: unzip_result = true; ferencd@0: # endif // !USE_SHELL_API ferencd@0: if (unzip_result) ferencd@0: delete_file(gz_file); ferencd@0: ferencd@0: // Use the unzip program extract the data from the tar file that was ferencd@0: // just extracted from the archive. ferencd@0: auto tar_file = get_download_tar_file(version); ferencd@0: cmd = '\"'; ferencd@0: cmd += unzip_prog; ferencd@0: cmd += "\" x \""; ferencd@0: cmd += tar_file; ferencd@0: cmd += "\" -o\""; ferencd@0: cmd += get_install(); ferencd@0: cmd += '\"'; ferencd@0: # if USE_SHELL_API ferencd@0: cmd = "\"" + cmd + "\""; ferencd@0: if (std::system(cmd.c_str()) == EXIT_SUCCESS) ferencd@0: unzip_result = true; ferencd@0: # else // !USE_SHELL_API ferencd@0: if (run_program(cmd) == EXIT_SUCCESS) ferencd@0: unzip_result = true; ferencd@0: # endif // !USE_SHELL_API ferencd@0: ferencd@0: if (unzip_result) ferencd@0: delete_file(tar_file); ferencd@0: ferencd@0: return unzip_result; ferencd@0: } ferencd@0: ferencd@0: static ferencd@0: std::string ferencd@0: get_download_mapping_file(const std::string& version) ferencd@0: { ferencd@0: auto file = get_install() + version + "windowsZones.xml"; ferencd@0: return file; ferencd@0: } ferencd@0: ferencd@0: # else // !_WIN32 ferencd@0: ferencd@0: # if !USE_SHELL_API ferencd@0: static ferencd@0: int ferencd@0: run_program(const char* prog, const char*const args[]) ferencd@0: { ferencd@0: pid_t pid = fork(); ferencd@0: if (pid == -1) // Child failed to start. ferencd@0: return EXIT_FAILURE; ferencd@0: ferencd@0: if (pid != 0) ferencd@0: { ferencd@0: // We are in the parent. Child started. Wait for it. ferencd@0: pid_t ret; ferencd@0: int status; ferencd@0: while ((ret = waitpid(pid, &status, 0)) == -1) ferencd@0: { ferencd@0: if (errno != EINTR) ferencd@0: break; ferencd@0: } ferencd@0: if (ret != -1) ferencd@0: { ferencd@0: if (WIFEXITED(status)) ferencd@0: return WEXITSTATUS(status); ferencd@0: } ferencd@0: printf("Child issues!\n"); ferencd@0: ferencd@0: return EXIT_FAILURE; // Not sure what status of child is. ferencd@0: } ferencd@0: else // We are in the child process. Start the program the parent wants to run. ferencd@0: { ferencd@0: ferencd@0: if (execv(prog, const_cast(args)) == -1) // Does not return. ferencd@0: { ferencd@0: perror("unreachable 0\n"); ferencd@0: _Exit(127); ferencd@0: } ferencd@0: printf("unreachable 2\n"); ferencd@0: } ferencd@0: printf("unreachable 2\n"); ferencd@0: // Unreachable. ferencd@0: assert(false); ferencd@0: exit(EXIT_FAILURE); ferencd@0: return EXIT_FAILURE; ferencd@0: } ferencd@0: # endif // !USE_SHELL_API ferencd@0: ferencd@0: static ferencd@0: bool ferencd@0: extract_gz_file(const std::string&, const std::string& gz_file, const std::string&) ferencd@0: { ferencd@0: # if USE_SHELL_API ferencd@0: bool unzipped = std::system(("tar -xzf " + gz_file + " -C " + get_install()).c_str()) == EXIT_SUCCESS; ferencd@0: # else // !USE_SHELL_API ferencd@0: const char prog[] = {"/usr/bin/tar"}; ferencd@0: const char*const args[] = ferencd@0: { ferencd@0: prog, "-xzf", gz_file.c_str(), "-C", get_install().c_str(), nullptr ferencd@0: }; ferencd@0: bool unzipped = (run_program(prog, args) == EXIT_SUCCESS); ferencd@0: # endif // !USE_SHELL_API ferencd@0: if (unzipped) ferencd@0: { ferencd@0: delete_file(gz_file); ferencd@0: return true; ferencd@0: } ferencd@0: return false; ferencd@0: } ferencd@0: ferencd@0: # endif // !_WIN32 ferencd@0: ferencd@0: bool ferencd@0: remote_download(const std::string& version) ferencd@0: { ferencd@0: assert(!version.empty()); ferencd@0: ferencd@0: # ifdef _WIN32 ferencd@0: // Download folder should be always available for Windows ferencd@0: # else // !_WIN32 ferencd@0: // Create download folder if it does not exist on UNIX system ferencd@0: auto download_folder = get_install(); ferencd@0: if (!file_exists(download_folder)) ferencd@0: { ferencd@0: if (!make_directory(download_folder)) ferencd@0: return false; ferencd@0: } ferencd@0: # endif // _WIN32 ferencd@0: ferencd@0: auto url = "https://data.iana.org/time-zones/releases/tzdata" + version + ferencd@0: ".tar.gz"; ferencd@0: bool result = download_to_file(url, get_download_gz_file(version), ferencd@0: download_file_options::binary); ferencd@0: # ifdef _WIN32 ferencd@0: if (result) ferencd@0: { ferencd@0: auto mapping_file = get_download_mapping_file(version); ferencd@0: result = download_to_file( ferencd@0: "https://raw.githubusercontent.com/unicode-org/cldr/master/" ferencd@0: "common/supplemental/windowsZones.xml", ferencd@0: mapping_file, download_file_options::text); ferencd@0: } ferencd@0: # endif // _WIN32 ferencd@0: return result; ferencd@0: } ferencd@0: ferencd@0: bool ferencd@0: remote_install(const std::string& version) ferencd@0: { ferencd@0: auto success = false; ferencd@0: assert(!version.empty()); ferencd@0: ferencd@0: std::string install = get_install(); ferencd@0: auto gz_file = get_download_gz_file(version); ferencd@0: if (file_exists(gz_file)) ferencd@0: { ferencd@0: if (file_exists(install)) ferencd@0: remove_folder_and_subfolders(install); ferencd@0: if (make_directory(install)) ferencd@0: { ferencd@0: if (extract_gz_file(version, gz_file, install)) ferencd@0: success = true; ferencd@0: # ifdef _WIN32 ferencd@0: auto mapping_file_source = get_download_mapping_file(version); ferencd@0: auto mapping_file_dest = get_install(); ferencd@0: mapping_file_dest += folder_delimiter; ferencd@0: mapping_file_dest += "windowsZones.xml"; ferencd@0: if (!move_file(mapping_file_source, mapping_file_dest)) ferencd@0: success = false; ferencd@0: # endif // _WIN32 ferencd@0: } ferencd@0: } ferencd@0: return success; ferencd@0: } ferencd@0: ferencd@0: #endif // HAS_REMOTE_API ferencd@0: ferencd@0: static ferencd@0: std::string ferencd@0: get_version(const std::string& path) ferencd@0: { ferencd@0: std::string version; ferencd@0: std::ifstream infile(path + "version"); ferencd@0: if (infile.is_open()) ferencd@0: { ferencd@0: infile >> version; ferencd@0: if (!infile.fail()) ferencd@0: return version; ferencd@0: } ferencd@0: else ferencd@0: { ferencd@0: infile.open(path + "NEWS"); ferencd@0: while (infile) ferencd@0: { ferencd@0: infile >> version; ferencd@0: if (version == "Release") ferencd@0: { ferencd@0: infile >> version; ferencd@0: return version; ferencd@0: } ferencd@0: } ferencd@0: } ferencd@0: throw std::runtime_error("Unable to get Timezone database version from " + path); ferencd@0: } ferencd@0: ferencd@0: static ferencd@0: std::unique_ptr ferencd@0: init_tzdb() ferencd@0: { ferencd@0: using namespace date; ferencd@0: const std::string install = get_install(); ferencd@0: const std::string path = install + folder_delimiter; ferencd@0: std::string line; ferencd@0: bool continue_zone = false; ferencd@0: std::unique_ptr db(new tzdb); ferencd@0: ferencd@0: #if AUTO_DOWNLOAD ferencd@0: if (!file_exists(install)) ferencd@0: { ferencd@0: auto rv = remote_version(); ferencd@0: if (!rv.empty() && remote_download(rv)) ferencd@0: { ferencd@0: if (!remote_install(rv)) ferencd@0: { ferencd@0: std::string msg = "Timezone database version \""; ferencd@0: msg += rv; ferencd@0: msg += "\" did not install correctly to \""; ferencd@0: msg += install; ferencd@0: msg += "\""; ferencd@0: throw std::runtime_error(msg); ferencd@0: } ferencd@0: } ferencd@0: if (!file_exists(install)) ferencd@0: { ferencd@0: std::string msg = "Timezone database not found at \""; ferencd@0: msg += install; ferencd@0: msg += "\""; ferencd@0: throw std::runtime_error(msg); ferencd@0: } ferencd@0: db->version = get_version(path); ferencd@0: } ferencd@0: else ferencd@0: { ferencd@0: db->version = get_version(path); ferencd@0: auto rv = remote_version(); ferencd@0: if (!rv.empty() && db->version != rv) ferencd@0: { ferencd@0: if (remote_download(rv)) ferencd@0: { ferencd@0: remote_install(rv); ferencd@0: db->version = get_version(path); ferencd@0: } ferencd@0: } ferencd@0: } ferencd@0: #else // !AUTO_DOWNLOAD ferencd@0: if (!file_exists(install)) ferencd@0: { ferencd@0: std::string msg = "Timezone database not found at \""; ferencd@0: msg += install; ferencd@0: msg += "\""; ferencd@0: throw std::runtime_error(msg); ferencd@0: } ferencd@0: db->version = get_version(path); ferencd@0: #endif // !AUTO_DOWNLOAD ferencd@0: ferencd@0: CONSTDATA char*const files[] = ferencd@0: { ferencd@0: "africa", "antarctica", "asia", "australasia", "backward", "etcetera", "europe", ferencd@0: "pacificnew", "northamerica", "southamerica", "systemv", "leapseconds" ferencd@0: }; ferencd@0: ferencd@0: for (const auto& filename : files) ferencd@0: { ferencd@0: std::ifstream infile(path + filename); ferencd@0: while (infile) ferencd@0: { ferencd@0: std::getline(infile, line); ferencd@0: if (!line.empty() && line[0] != '#') ferencd@0: { ferencd@0: std::istringstream in(line); ferencd@0: std::string word; ferencd@0: in >> word; ferencd@0: if (word == "Rule") ferencd@0: { ferencd@0: db->rules.push_back(Rule(line)); ferencd@0: continue_zone = false; ferencd@0: } ferencd@0: else if (word == "Link") ferencd@0: { ferencd@0: db->links.push_back(link(line)); ferencd@0: continue_zone = false; ferencd@0: } ferencd@0: else if (word == "Leap") ferencd@0: { ferencd@0: db->leaps.push_back(leap(line, detail::undocumented{})); ferencd@0: continue_zone = false; ferencd@0: } ferencd@0: else if (word == "Zone") ferencd@0: { ferencd@0: db->zones.push_back(time_zone(line, detail::undocumented{})); ferencd@0: continue_zone = true; ferencd@0: } ferencd@0: else if (line[0] == '\t' && continue_zone) ferencd@0: { ferencd@0: db->zones.back().add(line); ferencd@0: } ferencd@0: else ferencd@0: { ferencd@0: std::cerr << line << '\n'; ferencd@0: } ferencd@0: } ferencd@0: } ferencd@0: } ferencd@0: std::sort(db->rules.begin(), db->rules.end()); ferencd@0: Rule::split_overlaps(db->rules); ferencd@0: std::sort(db->zones.begin(), db->zones.end()); ferencd@0: db->zones.shrink_to_fit(); ferencd@0: std::sort(db->links.begin(), db->links.end()); ferencd@0: db->links.shrink_to_fit(); ferencd@0: std::sort(db->leaps.begin(), db->leaps.end()); ferencd@0: db->leaps.shrink_to_fit(); ferencd@0: ferencd@0: #ifdef _WIN32 ferencd@0: std::string mapping_file = get_install() + folder_delimiter + "windowsZones.xml"; ferencd@0: db->mappings = load_timezone_mappings_from_xml_file(mapping_file); ferencd@0: sort_zone_mappings(db->mappings); ferencd@0: #endif // _WIN32 ferencd@0: ferencd@0: return db; ferencd@0: } ferencd@0: ferencd@0: const tzdb& ferencd@0: reload_tzdb() ferencd@0: { ferencd@0: #if AUTO_DOWNLOAD ferencd@0: auto const& v = get_tzdb_list().front().version; ferencd@0: if (!v.empty() && v == remote_version()) ferencd@0: return get_tzdb_list().front(); ferencd@0: #endif // AUTO_DOWNLOAD ferencd@0: tzdb_list::undocumented_helper::push_front(get_tzdb_list(), init_tzdb().release()); ferencd@0: return get_tzdb_list().front(); ferencd@0: } ferencd@0: ferencd@0: #endif // !USE_OS_TZDB ferencd@0: ferencd@0: const tzdb& ferencd@0: get_tzdb() ferencd@0: { ferencd@0: return get_tzdb_list().front(); ferencd@0: } ferencd@0: ferencd@0: const time_zone* ferencd@0: #if HAS_STRING_VIEW ferencd@0: tzdb::locate_zone(std::string_view tz_name) const ferencd@0: #else ferencd@0: tzdb::locate_zone(const std::string& tz_name) const ferencd@0: #endif ferencd@0: { ferencd@0: auto zi = std::lower_bound(zones.begin(), zones.end(), tz_name, ferencd@0: #if HAS_STRING_VIEW ferencd@0: [](const time_zone& z, const std::string_view& nm) ferencd@0: #else ferencd@0: [](const time_zone& z, const std::string& nm) ferencd@0: #endif ferencd@0: { ferencd@0: return z.name() < nm; ferencd@0: }); ferencd@0: if (zi == zones.end() || zi->name() != tz_name) ferencd@0: { ferencd@0: #if !USE_OS_TZDB ferencd@0: auto li = std::lower_bound(links.begin(), links.end(), tz_name, ferencd@0: #if HAS_STRING_VIEW ferencd@0: [](const link& z, const std::string_view& nm) ferencd@0: #else ferencd@0: [](const link& z, const std::string& nm) ferencd@0: #endif ferencd@0: { ferencd@0: return z.name() < nm; ferencd@0: }); ferencd@0: if (li != links.end() && li->name() == tz_name) ferencd@0: { ferencd@0: zi = std::lower_bound(zones.begin(), zones.end(), li->target(), ferencd@0: [](const time_zone& z, const std::string& nm) ferencd@0: { ferencd@0: return z.name() < nm; ferencd@0: }); ferencd@0: if (zi != zones.end() && zi->name() == li->target()) ferencd@0: return &*zi; ferencd@0: } ferencd@0: #endif // !USE_OS_TZDB ferencd@0: throw std::runtime_error(std::string(tz_name) + " not found in timezone database"); ferencd@0: } ferencd@0: return &*zi; ferencd@0: } ferencd@0: ferencd@0: const time_zone* ferencd@0: #if HAS_STRING_VIEW ferencd@0: locate_zone(std::string_view tz_name) ferencd@0: #else ferencd@0: locate_zone(const std::string& tz_name) ferencd@0: #endif ferencd@0: { ferencd@0: return get_tzdb().locate_zone(tz_name); ferencd@0: } ferencd@0: ferencd@0: #if USE_OS_TZDB ferencd@0: ferencd@0: std::ostream& ferencd@0: operator<<(std::ostream& os, const tzdb& db) ferencd@0: { ferencd@0: os << "Version: " << db.version << "\n\n"; ferencd@0: for (const auto& x : db.zones) ferencd@0: os << x << '\n'; ferencd@0: #if !MISSING_LEAP_SECONDS ferencd@0: os << '\n'; ferencd@0: for (const auto& x : db.leaps) ferencd@0: os << x << '\n'; ferencd@0: #endif // !MISSING_LEAP_SECONDS ferencd@0: return os; ferencd@0: } ferencd@0: ferencd@0: #else // !USE_OS_TZDB ferencd@0: ferencd@0: std::ostream& ferencd@0: operator<<(std::ostream& os, const tzdb& db) ferencd@0: { ferencd@0: os << "Version: " << db.version << '\n'; ferencd@0: std::string title("--------------------------------------------" ferencd@0: "--------------------------------------------\n" ferencd@0: "Name ""Start Y ""End Y " ferencd@0: "Beginning ""Offset " ferencd@0: "Designator\n" ferencd@0: "--------------------------------------------" ferencd@0: "--------------------------------------------\n"); ferencd@0: int count = 0; ferencd@0: for (const auto& x : db.rules) ferencd@0: { ferencd@0: if (count++ % 50 == 0) ferencd@0: os << title; ferencd@0: os << x << '\n'; ferencd@0: } ferencd@0: os << '\n'; ferencd@0: title = std::string("---------------------------------------------------------" ferencd@0: "--------------------------------------------------------\n" ferencd@0: "Name ""Offset " ferencd@0: "Rule ""Abrev ""Until\n" ferencd@0: "---------------------------------------------------------" ferencd@0: "--------------------------------------------------------\n"); ferencd@0: count = 0; ferencd@0: for (const auto& x : db.zones) ferencd@0: { ferencd@0: if (count++ % 10 == 0) ferencd@0: os << title; ferencd@0: os << x << '\n'; ferencd@0: } ferencd@0: os << '\n'; ferencd@0: title = std::string("---------------------------------------------------------" ferencd@0: "--------------------------------------------------------\n" ferencd@0: "Alias ""To\n" ferencd@0: "---------------------------------------------------------" ferencd@0: "--------------------------------------------------------\n"); ferencd@0: count = 0; ferencd@0: for (const auto& x : db.links) ferencd@0: { ferencd@0: if (count++ % 45 == 0) ferencd@0: os << title; ferencd@0: os << x << '\n'; ferencd@0: } ferencd@0: os << '\n'; ferencd@0: title = std::string("---------------------------------------------------------" ferencd@0: "--------------------------------------------------------\n" ferencd@0: "Leap second on\n" ferencd@0: "---------------------------------------------------------" ferencd@0: "--------------------------------------------------------\n"); ferencd@0: os << title; ferencd@0: for (const auto& x : db.leaps) ferencd@0: os << x << '\n'; ferencd@0: return os; ferencd@0: } ferencd@0: ferencd@0: #endif // !USE_OS_TZDB ferencd@0: ferencd@0: // ----------------------- ferencd@0: ferencd@0: #ifdef _WIN32 ferencd@0: ferencd@0: static ferencd@0: std::string ferencd@0: getTimeZoneKeyName() ferencd@0: { ferencd@0: DYNAMIC_TIME_ZONE_INFORMATION dtzi{}; ferencd@0: auto result = GetDynamicTimeZoneInformation(&dtzi); ferencd@0: if (result == TIME_ZONE_ID_INVALID) ferencd@0: throw std::runtime_error("current_zone(): GetDynamicTimeZoneInformation()" ferencd@0: " reported TIME_ZONE_ID_INVALID."); ferencd@0: auto wlen = wcslen(dtzi.TimeZoneKeyName); ferencd@0: char buf[128] = {}; ferencd@0: assert(sizeof(buf) >= wlen+1); ferencd@0: wcstombs(buf, dtzi.TimeZoneKeyName, wlen); ferencd@0: if (strcmp(buf, "Coordinated Universal Time") == 0) ferencd@0: return "UTC"; ferencd@0: return buf; ferencd@0: } ferencd@0: ferencd@0: const time_zone* ferencd@0: tzdb::current_zone() const ferencd@0: { ferencd@0: std::string win_tzid = getTimeZoneKeyName(); ferencd@0: std::string standard_tzid; ferencd@0: if (!native_to_standard_timezone_name(win_tzid, standard_tzid)) ferencd@0: { ferencd@0: std::string msg; ferencd@0: msg = "current_zone() failed: A mapping from the Windows Time Zone id \""; ferencd@0: msg += win_tzid; ferencd@0: msg += "\" was not found in the time zone mapping database."; ferencd@0: throw std::runtime_error(msg); ferencd@0: } ferencd@0: return locate_zone(standard_tzid); ferencd@0: } ferencd@0: ferencd@0: #else // !_WIN32 ferencd@0: ferencd@0: const time_zone* ferencd@0: tzdb::current_zone() const ferencd@0: { ferencd@0: // On some OS's a file called /etc/localtime may ferencd@0: // exist and it may be either a real file ferencd@0: // containing time zone details or a symlink to such a file. ferencd@0: // On MacOS and BSD Unix if this file is a symlink it ferencd@0: // might resolve to a path like this: ferencd@0: // "/usr/share/zoneinfo/America/Los_Angeles" ferencd@0: // If it does, we try to determine the current ferencd@0: // timezone from the remainder of the path by removing the prefix ferencd@0: // and hoping the rest resolves to a valid timezone. ferencd@0: // It may not always work though. If it doesn't then an ferencd@0: // exception will be thrown by local_timezone. ferencd@0: // The path may also take a relative form: ferencd@0: // "../usr/share/zoneinfo/America/Los_Angeles". ferencd@0: { ferencd@0: struct stat sb; ferencd@0: CONSTDATA auto timezone = "/etc/localtime"; ferencd@0: if (lstat(timezone, &sb) == 0 && S_ISLNK(sb.st_mode) && sb.st_size > 0) { ferencd@0: using namespace std; ferencd@0: char rp[PATH_MAX+1] = {}; ferencd@0: if (realpath(timezone, rp) == nullptr) ferencd@0: throw system_error(errno, system_category(), "realpath() failed"); ferencd@0: #if HAS_STRING_VIEW ferencd@0: string_view result = rp; ferencd@0: CONSTDATA string_view zoneinfo = "/zoneinfo/"; ferencd@0: const size_t pos = result.rfind(zoneinfo); ferencd@0: if (pos == result.npos) ferencd@0: throw runtime_error( ferencd@0: "current_zone() failed to find \"/zoneinfo/\" in " + string(result)); ferencd@0: result.remove_prefix(pos + zoneinfo.size()); ferencd@0: #else ferencd@0: string result = rp; ferencd@0: CONSTDATA char zoneinfo[] = "/zoneinfo/"; ferencd@0: const size_t pos = result.rfind(zoneinfo); ferencd@0: if (pos == result.npos) ferencd@0: throw runtime_error( ferencd@0: "current_zone() failed to find \"/zoneinfo/\" in " + result); ferencd@0: result.erase(0, pos + sizeof(zoneinfo) - 1); ferencd@0: #endif ferencd@0: return locate_zone(result); ferencd@0: } ferencd@0: } ferencd@0: // On embedded systems e.g. buildroot with uclibc the timezone is linked ferencd@0: // into /etc/TZ which is a symlink to path like this: ferencd@0: // "/usr/share/zoneinfo/uclibc/America/Los_Angeles" ferencd@0: // If it does, we try to determine the current ferencd@0: // timezone from the remainder of the path by removing the prefix ferencd@0: // and hoping the rest resolves to valid timezone. ferencd@0: // It may not always work though. If it doesn't then an ferencd@0: // exception will be thrown by local_timezone. ferencd@0: // The path may also take a relative form: ferencd@0: // "../usr/share/zoneinfo/uclibc/America/Los_Angeles". ferencd@0: { ferencd@0: struct stat sb; ferencd@0: CONSTDATA auto timezone = "/etc/TZ"; ferencd@0: if (lstat(timezone, &sb) == 0 && S_ISLNK(sb.st_mode) && sb.st_size > 0) { ferencd@0: using namespace std; ferencd@0: string result; ferencd@0: char rp[PATH_MAX+1] = {}; ferencd@0: if (readlink(timezone, rp, sizeof(rp)-1) > 0) ferencd@0: result = string(rp); ferencd@0: else ferencd@0: throw system_error(errno, system_category(), "readlink() failed"); ferencd@0: ferencd@0: const size_t pos = result.find(get_tz_dir()); ferencd@0: if (pos != result.npos) ferencd@0: result.erase(0, get_tz_dir().size() + 1 + pos); ferencd@0: return locate_zone(result); ferencd@0: } ferencd@0: } ferencd@0: { ferencd@0: // On some versions of some linux distro's (e.g. Ubuntu), ferencd@0: // the current timezone might be in the first line of ferencd@0: // the /etc/timezone file. ferencd@0: std::ifstream timezone_file("/etc/timezone"); ferencd@0: if (timezone_file.is_open()) ferencd@0: { ferencd@0: std::string result; ferencd@0: std::getline(timezone_file, result); ferencd@0: if (!result.empty()) ferencd@0: return locate_zone(result); ferencd@0: } ferencd@0: // Fall through to try other means. ferencd@0: } ferencd@0: { ferencd@0: // On some versions of some bsd distro's (e.g. FreeBSD), ferencd@0: // the current timezone might be in the first line of ferencd@0: // the /var/db/zoneinfo file. ferencd@0: std::ifstream timezone_file("/var/db/zoneinfo"); ferencd@0: if (timezone_file.is_open()) ferencd@0: { ferencd@0: std::string result; ferencd@0: std::getline(timezone_file, result); ferencd@0: if (!result.empty()) ferencd@0: return locate_zone(result); ferencd@0: } ferencd@0: // Fall through to try other means. ferencd@0: } ferencd@0: { ferencd@0: // On some versions of some bsd distro's (e.g. iOS), ferencd@0: // it is not possible to use file based approach, ferencd@0: // we switch to system API, calling functions in ferencd@0: // CoreFoundation framework. ferencd@0: #if TARGET_OS_IPHONE ferencd@0: std::string result = date::iOSUtils::get_current_timezone(); ferencd@0: if (!result.empty()) ferencd@0: return locate_zone(result); ferencd@0: #endif ferencd@0: // Fall through to try other means. ferencd@0: } ferencd@0: { ferencd@0: // On some versions of some linux distro's (e.g. Red Hat), ferencd@0: // the current timezone might be in the first line of ferencd@0: // the /etc/sysconfig/clock file as: ferencd@0: // ZONE="US/Eastern" ferencd@0: std::ifstream timezone_file("/etc/sysconfig/clock"); ferencd@0: std::string result; ferencd@0: while (timezone_file) ferencd@0: { ferencd@0: std::getline(timezone_file, result); ferencd@0: auto p = result.find("ZONE=\""); ferencd@0: if (p != std::string::npos) ferencd@0: { ferencd@0: result.erase(p, p+6); ferencd@0: result.erase(result.rfind('"')); ferencd@0: return locate_zone(result); ferencd@0: } ferencd@0: } ferencd@0: // Fall through to try other means. ferencd@0: } ferencd@0: throw std::runtime_error("Could not get current timezone"); ferencd@0: } ferencd@0: ferencd@0: #endif // !_WIN32 ferencd@0: ferencd@0: const time_zone* ferencd@0: current_zone() ferencd@0: { ferencd@0: return get_tzdb().current_zone(); ferencd@0: } ferencd@0: ferencd@0: } // namespace date ferencd@0: ferencd@0: #if defined(__GNUC__) && __GNUC__ < 5 ferencd@0: # pragma GCC diagnostic pop ferencd@0: #endif