| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying | |
| file LICENSE.rst or https://cmake.org/licensing for details. */ | |
| #if !defined(_WIN32) && !defined(__sun) && !defined(__OpenBSD__) | |
| // POSIX APIs are needed | |
| // NOLINTNEXTLINE(bugprone-reserved-identifier) | |
| # define _POSIX_C_SOURCE 200809L | |
| #endif | |
| #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__QNX__) | |
| // For isascii | |
| // NOLINTNEXTLINE(bugprone-reserved-identifier) | |
| # define _XOPEN_SOURCE 700 | |
| #endif | |
| #if defined(__APPLE__) | |
| // Restore Darwin APIs removed by _POSIX_C_SOURCE: | |
| // aligned_alloc | |
| // timespec_get | |
| // NOLINTNEXTLINE(bugprone-reserved-identifier) | |
| # define _DARWIN_C_SOURCE | |
| #endif | |
| #include "cmTimestamp.h" | |
| #include <cstdlib> | |
| #include <cstring> | |
| #include <ctime> | |
| #include <sstream> | |
| #include <utility> | |
| #ifdef __MINGW32__ | |
| # include <libloaderapi.h> | |
| #endif | |
| #include <cm3p/uv.h> | |
| #include "cmStringAlgorithms.h" | |
| #include "cmSystemTools.h" | |
| std::string cmTimestamp::CurrentTime(cm::string_view formatString, | |
| bool utcFlag) const | |
| { | |
| // get current time with microsecond resolution | |
| uv_timeval64_t timeval; | |
| uv_gettimeofday(&timeval); | |
| auto currentTimeT = static_cast<time_t>(timeval.tv_sec); | |
| auto microseconds = static_cast<uint32_t>(timeval.tv_usec); | |
| // check for override via SOURCE_DATE_EPOCH for reproducible builds | |
| std::string source_date_epoch; | |
| cmSystemTools::GetEnv("SOURCE_DATE_EPOCH", source_date_epoch); | |
| if (!source_date_epoch.empty()) { | |
| std::istringstream iss(source_date_epoch); | |
| iss >> currentTimeT; | |
| if (iss.fail() || !iss.eof()) { | |
| cmSystemTools::Error("Cannot parse SOURCE_DATE_EPOCH as integer"); | |
| exit(27); | |
| } | |
| // SOURCE_DATE_EPOCH has only a resolution in the seconds range | |
| microseconds = 0; | |
| } | |
| if (currentTimeT == static_cast<time_t>(-1)) { | |
| return std::string(); | |
| } | |
| return this->CreateTimestampFromTimeT(currentTimeT, microseconds, | |
| static_cast<std::string>(formatString), | |
| utcFlag); | |
| } | |
| std::string cmTimestamp::FileModificationTime(char const* path, | |
| cm::string_view formatString, | |
| bool utcFlag) const | |
| { | |
| std::string real_path = | |
| cmSystemTools::GetRealPathResolvingWindowsSubst(path); | |
| if (!cmsys::SystemTools::FileExists(real_path)) { | |
| return std::string(); | |
| } | |
| // use libuv's implementation of stat(2) to get the file information | |
| time_t mtime = 0; | |
| uint32_t microseconds = 0; | |
| uv_fs_t req; | |
| if (uv_fs_stat(nullptr, &req, real_path.c_str(), nullptr) == 0) { | |
| mtime = static_cast<time_t>(req.statbuf.st_mtim.tv_sec); | |
| // tv_nsec has nanosecond resolution, but we truncate it to microsecond | |
| // resolution in order to be consistent with cmTimestamp::CurrentTime() | |
| microseconds = static_cast<uint32_t>(req.statbuf.st_mtim.tv_nsec / 1000); | |
| } | |
| uv_fs_req_cleanup(&req); | |
| return this->CreateTimestampFromTimeT( | |
| mtime, microseconds, static_cast<std::string>(formatString), utcFlag); | |
| } | |
| std::string cmTimestamp::CreateTimestampFromTimeT(time_t timeT, | |
| std::string formatString, | |
| bool utcFlag) const | |
| { | |
| return this->CreateTimestampFromTimeT(timeT, 0, std::move(formatString), | |
| utcFlag); | |
| } | |
| std::string cmTimestamp::CreateTimestampFromTimeT(time_t timeT, | |
| uint32_t const microseconds, | |
| std::string formatString, | |
| bool utcFlag) const | |
| { | |
| if (formatString.empty()) { | |
| formatString = "%Y-%m-%dT%H:%M:%S"; | |
| if (utcFlag) { | |
| formatString += "Z"; | |
| } | |
| } | |
| struct tm timeStruct; | |
| memset(&timeStruct, 0, sizeof(timeStruct)); | |
| struct tm* ptr = nullptr; | |
| if (utcFlag) { | |
| ptr = gmtime(&timeT); | |
| } else { | |
| ptr = localtime(&timeT); | |
| } | |
| if (!ptr) { | |
| return std::string(); | |
| } | |
| timeStruct = *ptr; | |
| std::string result; | |
| for (std::string::size_type i = 0; i < formatString.size(); ++i) { | |
| char c1 = formatString[i]; | |
| char c2 = (i + 1 < formatString.size()) ? formatString[i + 1] | |
| : static_cast<char>(0); | |
| if (c1 == '%' && c2 != 0) { | |
| result += this->AddTimestampComponent(c2, timeStruct, timeT, utcFlag, | |
| microseconds); | |
| ++i; | |
| } else { | |
| result += c1; | |
| } | |
| } | |
| return result; | |
| } | |
| time_t cmTimestamp::CreateUtcTimeTFromTm(struct tm& tm) const | |
| { | |
| #if defined(_MSC_VER) && _MSC_VER >= 1400 | |
| return _mkgmtime(&tm); | |
| #else | |
| // From Linux timegm() manpage. | |
| std::string tz_old; | |
| bool const tz_was_set = cmSystemTools::GetEnv("TZ", tz_old); | |
| tz_old = "TZ=" + tz_old; | |
| // The standard says that "TZ=" or "TZ=[UNRECOGNIZED_TZ]" means UTC. | |
| // It seems that "TZ=" does NOT work, at least under Windows | |
| // with neither MSVC nor MinGW, so let's use explicit "TZ=UTC" | |
| cmSystemTools::PutEnv("TZ=UTC"); | |
| tzset(); | |
| time_t result = mktime(&tm); | |
| # ifndef CMAKE_BOOTSTRAP | |
| if (tz_was_set) { | |
| cmSystemTools::PutEnv(tz_old); | |
| } else { | |
| cmSystemTools::UnsetEnv("TZ"); | |
| } | |
| # else | |
| // No UnsetEnv during bootstrap. This is good enough for CMake itself. | |
| cmSystemTools::PutEnv(tz_old); | |
| static_cast<void>(tz_was_set); | |
| # endif | |
| tzset(); | |
| return result; | |
| #endif | |
| } | |
| std::string cmTimestamp::AddTimestampComponent( | |
| char flag, struct tm& timeStruct, time_t const timeT, bool const utcFlag, | |
| uint32_t const microseconds) const | |
| { | |
| std::string formatString = cmStrCat('%', flag); | |
| switch (flag) { | |
| case 'a': | |
| case 'A': | |
| case 'b': | |
| case 'B': | |
| case 'd': | |
| case 'H': | |
| case 'I': | |
| case 'j': | |
| case 'm': | |
| case 'M': | |
| case 'S': | |
| case 'U': | |
| case 'V': | |
| case 'w': | |
| case 'y': | |
| case 'Y': | |
| case '%': | |
| break; | |
| case 'Z': | |
| #if defined(__GLIBC__) | |
| // 'struct tm' has the time zone, so strftime can honor UTC. | |
| static_cast<void>(utcFlag); | |
| #else | |
| // 'struct tm' may not have the time zone, so strftime may | |
| // use local time. Hard-code the UTC result. | |
| if (utcFlag) { | |
| return std::string("GMT"); | |
| } | |
| #endif | |
| break; | |
| case 'z': { | |
| #if defined(__GLIBC__) | |
| // 'struct tm' has the time zone, so strftime can honor UTC. | |
| static_cast<void>(utcFlag); | |
| #else | |
| // 'struct tm' may not have the time zone, so strftime may | |
| // use local time. Hard-code the UTC result. | |
| if (utcFlag) { | |
| return std::string("+0000"); | |
| } | |
| #endif | |
| #ifndef _AIX | |
| break; | |
| #else | |
| std::string xpg_sus_old; | |
| bool const xpg_sus_was_set = | |
| cmSystemTools::GetEnv("XPG_SUS_ENV", xpg_sus_old); | |
| if (xpg_sus_was_set && xpg_sus_old == "ON") { | |
| break; | |
| } | |
| xpg_sus_old = "XPG_SUS_ENV=" + xpg_sus_old; | |
| // On AIX systems, %z requires XPG_SUS_ENV=ON to work as desired. | |
| cmSystemTools::PutEnv("XPG_SUS_ENV=ON"); | |
| tzset(); | |
| char buffer[16]; | |
| size_t size = strftime(buffer, sizeof(buffer), "%z", &timeStruct); | |
| # ifndef CMAKE_BOOTSTRAP | |
| if (xpg_sus_was_set) { | |
| cmSystemTools::PutEnv(xpg_sus_old); | |
| } else { | |
| cmSystemTools::UnsetEnv("XPG_SUS_ENV"); | |
| } | |
| # else | |
| // No UnsetEnv during bootstrap. This is good enough for CMake itself. | |
| cmSystemTools::PutEnv(xpg_sus_old); | |
| static_cast<void>(xpg_sus_was_set); | |
| # endif | |
| tzset(); | |
| return std::string(buffer, size); | |
| #endif | |
| } | |
| case 's': // Seconds since UNIX epoch (midnight 1-jan-1970) | |
| { | |
| // Build a time_t for UNIX epoch and subtract from the input "timeT": | |
| struct tm tmUnixEpoch; | |
| memset(&tmUnixEpoch, 0, sizeof(tmUnixEpoch)); | |
| tmUnixEpoch.tm_mday = 1; | |
| tmUnixEpoch.tm_year = 1970 - 1900; | |
| time_t const unixEpoch = this->CreateUtcTimeTFromTm(tmUnixEpoch); | |
| if (unixEpoch == -1) { | |
| cmSystemTools::Error( | |
| "Error generating UNIX epoch in string(TIMESTAMP ...) or " | |
| "file(TIMESTAMP ...). Please, file a bug report against CMake"); | |
| return std::string(); | |
| } | |
| return std::to_string( | |
| static_cast<int64_t>(std::difftime(timeT, unixEpoch))); | |
| } | |
| case 'f': // microseconds | |
| { | |
| // clip number to 6 digits and pad with leading zeros | |
| std::string microsecs = std::to_string(microseconds % 1000000); | |
| return std::string(6 - microsecs.length(), '0') + microsecs; | |
| } | |
| default: { | |
| return formatString; | |
| } | |
| } | |
| char buffer[16]; | |
| #ifdef __MINGW32__ | |
| /* See a bug in MinGW: https://sourceforge.net/p/mingw-w64/bugs/793/. A work | |
| * around is to try to use strftime() from ucrtbase.dll. */ | |
| using T = size_t(__cdecl*)(char*, size_t, char const*, const struct tm*); | |
| auto loadUcrtStrftime = []() -> T { | |
| auto handle = | |
| LoadLibraryExA("ucrtbase.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); | |
| if (handle) { | |
| # pragma GCC diagnostic push | |
| # pragma GCC diagnostic ignored "-Wcast-function-type" | |
| return reinterpret_cast<T>(GetProcAddress(handle, "strftime")); | |
| # pragma GCC diagnostic pop | |
| } | |
| return nullptr; | |
| }; | |
| static T ucrtStrftime = loadUcrtStrftime(); | |
| if (ucrtStrftime) { | |
| size_t size = | |
| ucrtStrftime(buffer, sizeof(buffer), formatString.c_str(), &timeStruct); | |
| return std::string(buffer, size); | |
| } | |
| #endif | |
| size_t size = | |
| strftime(buffer, sizeof(buffer), formatString.c_str(), &timeStruct); | |
| return std::string(buffer, size); | |
| } |