1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
4 #if !defined(_WIN32) && !defined(__sun) && !defined(__OpenBSD__)
5 // POSIX APIs are needed
6 // NOLINTNEXTLINE(bugprone-reserved-identifier)
7 # define _POSIX_C_SOURCE 200809L
9 #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__QNX__)
11 // NOLINTNEXTLINE(bugprone-reserved-identifier)
12 # define _XOPEN_SOURCE 700
15 #include "cmTimestamp.h"
23 # include <libloaderapi.h>
28 #include "cmStringAlgorithms.h"
29 #include "cmSystemTools.h"
31 std::string cmTimestamp::CurrentTime(const std::string& formatString,
34 // get current time with microsecond resolution
35 uv_timeval64_t timeval;
36 uv_gettimeofday(&timeval);
37 auto currentTimeT = static_cast<time_t>(timeval.tv_sec);
38 auto microseconds = static_cast<uint32_t>(timeval.tv_usec);
40 // check for override via SOURCE_DATE_EPOCH for reproducible builds
41 std::string source_date_epoch;
42 cmSystemTools::GetEnv("SOURCE_DATE_EPOCH", source_date_epoch);
43 if (!source_date_epoch.empty()) {
44 std::istringstream iss(source_date_epoch);
46 if (iss.fail() || !iss.eof()) {
47 cmSystemTools::Error("Cannot parse SOURCE_DATE_EPOCH as integer");
50 // SOURCE_DATE_EPOCH has only a resolution in the seconds range
53 if (currentTimeT == static_cast<time_t>(-1)) {
57 return this->CreateTimestampFromTimeT(currentTimeT, microseconds,
58 formatString, utcFlag);
61 std::string cmTimestamp::FileModificationTime(const char* path,
62 const std::string& formatString,
65 std::string real_path =
66 cmSystemTools::GetRealPathResolvingWindowsSubst(path);
68 if (!cmsys::SystemTools::FileExists(real_path)) {
72 // use libuv's implementation of stat(2) to get the file information
74 uint32_t microseconds = 0;
76 if (uv_fs_stat(nullptr, &req, real_path.c_str(), nullptr) == 0) {
77 mtime = static_cast<time_t>(req.statbuf.st_mtim.tv_sec);
78 // tv_nsec has nanosecond resolution, but we truncate it to microsecond
79 // resolution in order to be consistent with cmTimestamp::CurrentTime()
80 microseconds = static_cast<uint32_t>(req.statbuf.st_mtim.tv_nsec / 1000);
82 uv_fs_req_cleanup(&req);
84 return this->CreateTimestampFromTimeT(mtime, microseconds, formatString,
88 std::string cmTimestamp::CreateTimestampFromTimeT(time_t timeT,
89 std::string formatString,
92 return this->CreateTimestampFromTimeT(timeT, 0, std::move(formatString),
96 std::string cmTimestamp::CreateTimestampFromTimeT(time_t timeT,
97 const uint32_t microseconds,
98 std::string formatString,
101 if (formatString.empty()) {
102 formatString = "%Y-%m-%dT%H:%M:%S";
108 struct tm timeStruct;
109 memset(&timeStruct, 0, sizeof(timeStruct));
111 struct tm* ptr = nullptr;
113 ptr = gmtime(&timeT);
115 ptr = localtime(&timeT);
118 if (ptr == nullptr) {
119 return std::string();
125 for (std::string::size_type i = 0; i < formatString.size(); ++i) {
126 char c1 = formatString[i];
127 char c2 = (i + 1 < formatString.size()) ? formatString[i + 1]
128 : static_cast<char>(0);
130 if (c1 == '%' && c2 != 0) {
132 this->AddTimestampComponent(c2, timeStruct, timeT, microseconds);
142 time_t cmTimestamp::CreateUtcTimeTFromTm(struct tm& tm) const
144 #if defined(_MSC_VER) && _MSC_VER >= 1400
145 return _mkgmtime(&tm);
147 // From Linux timegm() manpage.
150 bool const tz_was_set = cmSystemTools::GetEnv("TZ", tz_old);
151 tz_old = "TZ=" + tz_old;
153 // The standard says that "TZ=" or "TZ=[UNRECOGNIZED_TZ]" means UTC.
154 // It seems that "TZ=" does NOT work, at least under Windows
155 // with neither MSVC nor MinGW, so let's use explicit "TZ=UTC"
157 cmSystemTools::PutEnv("TZ=UTC");
161 time_t result = mktime(&tm);
163 # ifndef CMAKE_BOOTSTRAP
165 cmSystemTools::PutEnv(tz_old);
167 cmSystemTools::UnsetEnv("TZ");
170 // No UnsetEnv during bootstrap. This is good enough for CMake itself.
171 cmSystemTools::PutEnv(tz_old);
172 static_cast<void>(tz_was_set);
181 std::string cmTimestamp::AddTimestampComponent(
182 char flag, struct tm& timeStruct, const time_t timeT,
183 const uint32_t microseconds) const
185 std::string formatString = cmStrCat('%', flag);
206 case 's': // Seconds since UNIX epoch (midnight 1-jan-1970)
208 // Build a time_t for UNIX epoch and subtract from the input "timeT":
209 struct tm tmUnixEpoch;
210 memset(&tmUnixEpoch, 0, sizeof(tmUnixEpoch));
211 tmUnixEpoch.tm_mday = 1;
212 tmUnixEpoch.tm_year = 1970 - 1900;
214 const time_t unixEpoch = this->CreateUtcTimeTFromTm(tmUnixEpoch);
215 if (unixEpoch == -1) {
216 cmSystemTools::Error(
217 "Error generating UNIX epoch in string(TIMESTAMP ...) or "
218 "file(TIMESTAMP ...). Please, file a bug report against CMake");
219 return std::string();
222 return std::to_string(static_cast<long int>(difftime(timeT, unixEpoch)));
224 case 'f': // microseconds
226 // clip number to 6 digits and pad with leading zeros
227 std::string microsecs = std::to_string(microseconds % 1000000);
228 return std::string(6 - microsecs.length(), '0') + microsecs;
238 /* See a bug in MinGW: https://sourceforge.net/p/mingw-w64/bugs/793/. A work
239 * around is to try to use strftime() from ucrtbase.dll. */
240 using T = size_t(__cdecl*)(char*, size_t, const char*, const struct tm*);
241 auto loadUcrtStrftime = []() -> T {
243 LoadLibraryExA("ucrtbase.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
245 # pragma GCC diagnostic push
246 # pragma GCC diagnostic ignored "-Wcast-function-type"
247 return reinterpret_cast<T>(GetProcAddress(handle, "strftime"));
248 # pragma GCC diagnostic pop
252 static T ucrtStrftime = loadUcrtStrftime();
256 ucrtStrftime(buffer, sizeof(buffer), formatString.c_str(), &timeStruct);
257 return std::string(buffer, size);
262 strftime(buffer, sizeof(buffer), formatString.c_str(), &timeStruct);
264 return std::string(buffer, size);