// The MIT License (MIT) // // Copyright (c) 2015, 2016 Howard Hinnant // Copyright (c) 2015 Ville Voutilainen // Copyright (c) 2016 Alexander Kormanovsky // Copyright (c) 2016, 2017 Jiangang Zhuang // // 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 woud 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 eearly so Windows.h doesn't get included // without these macros having been defined. // min/max macrosinterfere 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 // _WIN32 // 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 cures see: // https://support.microsoft.com/en-us/kb/130869 #include // But with included, the error moves on to: // error: 'FOLDERID_Downloads' was not declared in this scope // Which #include cures. #include #endif // __MINGW32__ #include #endif // _WIN32 #include "tz_private.h" #include "ios.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #include #endif // _WIN32 // unistd.h is used on some platforms as part of the the means to get // the current time zone. On Win32 Windows.h provides a means to do it. // gcc/mingw supports unistd.h on Win32 but MSVC does not. #ifdef _WIN32 # include // _unlink etc. # include // CoTaskFree, ShGetKnownFolderPath etc. # if HAS_REMOTE_API # include // _mkdir # include // ShFileOperation etc. # endif // HAS_REMOTE_API #else // !WIN32 # include # include # include # include # if !USE_SHELL_API # include # include # include # include # include # include # 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 effect windows.h such as NOMINMAX. #include #endif #ifdef _WIN32 static CONSTDATA char folder_delimiter = '\\'; #else static CONSTDATA char folder_delimiter = '/'; #endif #ifdef _WIN32 namespace { struct task_mem_deleter { void operator()(wchar_t buf[]) { if (buf != nullptr) CoTaskMemFree(buf); } }; using co_task_mem_ptr = std::unique_ptr; } // 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, NULL, &pfolder); if (SUCCEEDED(hr)) { co_task_mem_ptr folder_ptr(pfolder); folder = std::string(folder_ptr.get(), folder_ptr.get() + wcslen(folder_ptr.get())); } return folder; } // Usually something like "c:\Program Files". static std::string get_program_folder() { return get_known_folder(FOLDERID_ProgramFiles); } // Usually something like "c:\Users\username\Downloads". static std::string get_download_folder() { return get_known_folder(FOLDERID_Downloads); } #else // !_WIN32 static std::string expand_path(std::string path) { #if TARGET_OS_IPHONE return date::iOSUtils::get_tzdata_path(); #else ::wordexp_t w{}; ::wordexp(path.c_str(), &w, 0); assert(w.we_wordc == 1); path = w.we_wordv[0]; ::wordfree(&w); return path; #endif } #endif // !_WIN32 namespace date { // +---------------------+ // | Begin Configuration | // +---------------------+ using namespace detail; static std::string& access_install() { static std::string install #ifndef INSTALL # ifdef _WIN32 = get_download_folder() + folder_delimiter + "tzdata"; # else = expand_path("~/Downloads/tzdata"); # endif #else // INSTALL # define STRINGIZEIMP(x) #x # define STRINGIZE(x) STRINGIZEIMP(x) = STRINGIZE(INSTALL) + std::string(1, folder_delimiter) + "tzdata"; #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; } static std::string get_download_gz_file(const std::string& version) { auto file = get_install() + version + ".tar.gz"; return file; } // These can be used to reduce the range of the database to save memory CONSTDATA auto min_year = date::year::min(); CONSTDATA auto max_year = date::year::max(); CONSTDATA auto min_day = date::jan/1; CONSTDATA auto max_day = date::dec/31; // +-------------------+ // | End Configuration | // +-------------------+ namespace detail { struct undocumented {explicit undocumented() = default;}; } #ifndef _MSC_VER static_assert(min_year <= max_year, "Configuration error"); #endif #ifdef TIMEZONE_MAPPING namespace // Put types in an anonymous name space. { const std::string& get_windows_zones_install() { static const std::string install #ifndef WINDOWSZONES_INSTALL = get_install(); #else # define STRINGIZEIMP(x) #x # define STRINGIZE(x) STRINGIZEIMP(x) = STRINGIZE(WINDOWSZONES_INSTALL); #endif std::cout << install << std::endl; return install; } // A simple type to manage RAII for key handles and to // implement the trivial registry interface we need. // Not intended to be general-purpose. class reg_key { private: // Note there is no value documented to be an invalid handle value. // Not NULL nor INVALID_HANDLE_VALUE. We must rely on is_open. HKEY m_key = nullptr; bool m_is_open = false; public: ~reg_key() { close(); } reg_key() = default; reg_key(const reg_key&) = delete; reg_key& operator=(const reg_key&) = delete; HKEY handle() { return m_key; } bool is_open() const { return m_is_open; } LONG open(const wchar_t* key_name) { LONG result; result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, key_name, 0, KEY_READ, &m_key); if (result == ERROR_SUCCESS) m_is_open = true; return result; } LONG close() { if (m_is_open) { auto result = RegCloseKey(m_key); assert(result == ERROR_SUCCESS); if (result == ERROR_SUCCESS) { m_is_open = false; m_key = nullptr; } return result; } return ERROR_SUCCESS; } // WARNING: this function is not a general-purpose function. // It has a hard-coded value size limit that should be sufficient for our use cases. bool get_string(const wchar_t* key_name, std::string& value, std::wstring_convert>& converter) { value.clear(); wchar_t value_buffer[256]; // 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; if (RegQueryValueExW(handle(), key_name, nullptr, &tzi_type, reinterpret_cast(value_buffer), &size) == ERROR_SUCCESS) { // Function does not guarantee to null terminate. value_buffer[size/sizeof(value_buffer[0])] = L'\0'; value = converter.to_bytes(value_buffer); return true; } return false; } bool get_binary(const wchar_t* key_name, void* value, int value_size) { DWORD size = value_size; DWORD type = REG_BINARY; if (RegQueryValueExW(handle(), key_name, nullptr, &type, reinterpret_cast(value), &size) == ERROR_SUCCESS && (int) size == value_size) return true; return false; } }; } // anonymous namespace static std::string get_download_mapping_file(const std::string& version) { auto file = get_install() + version + "windowsZones.xml"; return file; } // Parse this XML file: // http://unicode.org/repos/cldr/trunk/common/supplemental/windowsZones.xml // The parsing method is designed to be simple and quick. It is not overly // forgiving of change but it should diagnose basic format issues. // See timezone_mapping structure for more info. static std::vector load_timezone_mappings_from_xml_file(const std::string& input_path) { std::size_t line_num = 0; std::vector 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_num, &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; bool mapZoneOpenTagFound = false; bool mapTZoneCloseTagFound = false; std::size_t mapZonePos = std::string::npos; std::size_t mapTimezonesPos = std::string::npos; CONSTDATA char mapTimeZonesOpeningTag[] = { ""); mapTimezonesCloseTagFound = (mapTimezonesPos != std::string::npos); if (!mapTimezonesCloseTagFound) { std::size_t commentPos = line.find(" " << 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)); } std::ostream& operator<<(std::ostream& os, const leap& x) { using namespace date; return os << x.date_ << " +"; } 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; } static const auto curl_delete = [](CURL* p) {::curl_easy_cleanup(p);}; static std::unique_ptr curl_init() { static const auto curl_is_now_initiailized = curl_global(); (void)curl_is_now_initiailized; return std::unique_ptr{::curl_easy_init(), curl_delete}; } static bool download_to_string(const std::string& url, std::string& str) { str.clear(); auto curl = curl_init(); if (!curl) return false; std::string version; curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str()); curl_write_callback write_cb = [](char* contents, std::size_t size, std::size_t nmemb, void* userp) -> std::size_t { auto& str = *static_cast(userp); auto realsize = size * nmemb; str.append(contents, realsize); return realsize; }; curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, write_cb); curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &str); 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_write_callback write_cb = [](char* contents, std::size_t size, std::size_t nmemb, void* userp) -> std::size_t { auto& of = *static_cast(userp); auto realsize = size * nmemb; of.write(contents, 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("http://www.iana.org/time-zones", str)) { CONSTDATA char db[] = "/time-zones/repository/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; } bool remote_download(const std::string& version) { assert(!version.empty()); auto url = "http://www.iana.org/time-zones/repository/releases/tzdata" + version + ".tar.gz"; bool result = download_to_file(url, get_download_gz_file(version), download_file_options::binary); #ifdef TIMEZONE_MAPPING if (result) { auto mapping_file = get_download_mapping_file(version); result = download_to_file("http://unicode.org/repos/cldr/trunk/common/" "supplemental/windowsZones.xml", mapping_file, download_file_options::text); } #endif return result; } // 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 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 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; 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( 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 " + folder).c_str()) == EXIT_SUCCESS; # else // !USE_SHELL_API return mkdir(folder.c_str(), 0777) == 0; # endif // !USE_SHELL_API #endif } 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 TIMEZONE_MAPPING static bool move_file(const std::string& from, const std::string& to) { #ifdef _WIN32 # 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 #else // !WIN32 # if USE_SHELL_API return std::system(("mv " + from + " " + to).c_str()) == EXIT_SUCCESS; # else return rename(from, to) == 0); # endif #endif // !WIN32 } #endif // TIMEZONE_MAPPING #ifdef _WIN32 // 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(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(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 neccessary 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; } #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(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_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 TIMEZONE_MAPPING auto mapping_file_source = get_download_mapping_file(version); auto mapping_file_dest = get_windows_zones_install(); mapping_file_dest += folder_delimiter; mapping_file_dest += "windowsZones.xml"; if (!move_file(mapping_file_source, mapping_file_dest)) success = false; #endif } } 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 TZ_DB 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; TZ_DB db; #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()); #if !LAZY_INIT for (auto& z : db.zones) z.adjust_infos(db.rules); #endif 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 TIMEZONE_MAPPING std::string mapping_file = get_windows_zones_install() + folder_delimiter + "windowsZones.xml"; db.mappings = load_timezone_mappings_from_xml_file(mapping_file); sort_zone_mappings(db.mappings); get_windows_timezone_info(db.native_zones); #endif // TIMEZONE_MAPPING return db; } static TZ_DB& access_tzdb() { static TZ_DB tz_db; return tz_db; } const TZ_DB& reload_tzdb() { #if AUTO_DOWNLOAD auto const& v = access_tzdb().version; if (!v.empty() && v == remote_version()) return access_tzdb(); #endif return access_tzdb() = init_tzdb(); } const TZ_DB& get_tzdb() { static const TZ_DB& ref = access_tzdb() = init_tzdb(); return ref; } const time_zone* locate_zone(const std::string& tz_name) { const auto& db = get_tzdb(); auto zi = std::lower_bound(db.zones.begin(), db.zones.end(), tz_name, [](const time_zone& z, const std::string& nm) { return z.name() < nm; }); if (zi == db.zones.end() || zi->name() != tz_name) { auto li = std::lower_bound(db.links.begin(), db.links.end(), tz_name, [](const link& z, const std::string& nm) { return z.name() < nm; }); if (li != db.links.end() && li->name() == tz_name) { zi = std::lower_bound(db.zones.begin(), db.zones.end(), li->target(), [](const time_zone& z, const std::string& nm) { return z.name() < nm; }); if (zi != db.zones.end() && zi->name() == li->target()) return &*zi; } throw std::runtime_error(tz_name + " not found in timezone database"); } return &*zi; } std::ostream& operator<<(std::ostream& os, const TZ_DB& 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; } // ----------------------- #ifdef _WIN32 const time_zone* current_zone() { #ifdef TIMEZONE_MAPPING TIME_ZONE_INFORMATION tzi{}; DWORD tz_result = ::GetTimeZoneInformation(&tzi); if (tz_result == TIME_ZONE_ID_INVALID) { auto error_code = ::GetLastError(); // Store this quick before it gets overwritten. throw std::runtime_error("GetTimeZoneInformation failed: " + get_win32_message(error_code)); } std::wstring_convert> converter; std::string standard_name(converter.to_bytes(tzi.StandardName)); auto tz = find_native_timezone_by_standard_name(standard_name); if (!tz) { std::string msg; msg = "current_zone() failed: "; msg += standard_name; msg += " was not found in the Windows Time Zone registry"; throw std::runtime_error( msg ); } std::string standard_tzid; if (!native_to_standard_timezone_name(tz->timezone_id, standard_tzid)) { std::string msg; msg = "current_zone() failed: A mapping from the Windows Time Zone id \""; msg += tz->timezone_id; msg += "\" was not found in the time zone mapping database."; throw std::runtime_error(msg); } return date::locate_zone(standard_tzid); #else // !TIMEZONE_MAPPING // Currently Win32 requires iana <--> windows tz name mappings // for this function to work. // TODO! we should really support TIMEZONE_MAPPINGS=0 on Windows, // And in this mode we should read the current iana timezone from a file. // This would allow the TZ library do be used by apps that don't care // about Windows standard names just iana names. // This would allow the xml dependency to be dropped and none of // the name mapping functions would be needed. throw std::runtime_error("current_zone not implemented."); #endif // !TIMEZONE_MAPPING } #else // !WIN32 const time_zone* current_zone() { // 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 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) { std::string result; char rp[PATH_MAX]; if (realpath(timezone, rp)) result = std::string(rp); else { std::ostringstream os; char message[128]; strerror_r(errno, message, 128); os << "realpath failure: errno = " << errno << "; " << message; throw std::runtime_error(os.str()); } const char zonepath[] = "/usr/share/zoneinfo/"; const std::size_t zonepath_len = sizeof(zonepath)/sizeof(zonepath[0])-1; const std::size_t pos = result.find(zonepath); if (pos != result.npos) result.erase(0, zonepath_len+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 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 #if defined(TZ_TEST) && defined(TIMEZONE_MAPPING) const time_zone* locate_native_zone(const std::string& native_tz_name) { std::string standard_tz_name; if (!native_to_standard_timezone_name(native_tz_name, standard_tz_name)) { std::string msg; msg = "locate_native_zone() failed: A mapping from the native/Windows Time Zone id \""; msg += native_tz_name; msg += "\" was not found in the time zone mapping database."; throw std::runtime_error(msg); } return locate_zone(standard_tz_name); } #endif // TZ_TEST && TIMEZONE_MAPPING } // namespace date