Coverage Report

Created: 2026-03-12 06:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmTimestamp.cxx
Line
Count
Source
1
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2
   file LICENSE.rst or https://cmake.org/licensing for details.  */
3
4
#if !defined(_WIN32) && !defined(__sun) && !defined(__OpenBSD__)
5
// POSIX APIs are needed
6
// NOLINTNEXTLINE(bugprone-reserved-identifier)
7
#  define _POSIX_C_SOURCE 200809L
8
#endif
9
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__QNX__)
10
// For isascii
11
// NOLINTNEXTLINE(bugprone-reserved-identifier)
12
#  define _XOPEN_SOURCE 700
13
#endif
14
#if defined(__APPLE__)
15
// Restore Darwin APIs removed by _POSIX_C_SOURCE:
16
//   aligned_alloc
17
//   timespec_get
18
// NOLINTNEXTLINE(bugprone-reserved-identifier)
19
#  define _DARWIN_C_SOURCE
20
#endif
21
22
#include "cmTimestamp.h"
23
24
#include <cstdlib>
25
#include <cstring>
26
#include <ctime>
27
#include <sstream>
28
#include <utility>
29
30
#ifdef __MINGW32__
31
#  include <libloaderapi.h>
32
#endif
33
34
#include <cm3p/uv.h>
35
36
#include "cmStringAlgorithms.h"
37
#include "cmSystemTools.h"
38
39
std::string cmTimestamp::CurrentTime(cm::string_view formatString,
40
                                     bool utcFlag) const
41
0
{
42
  // get current time with microsecond resolution
43
0
  uv_timeval64_t timeval;
44
0
  uv_gettimeofday(&timeval);
45
0
  auto currentTimeT = static_cast<time_t>(timeval.tv_sec);
46
0
  auto microseconds = static_cast<uint32_t>(timeval.tv_usec);
47
48
  // check for override via SOURCE_DATE_EPOCH for reproducible builds
49
0
  std::string source_date_epoch;
50
0
  cmSystemTools::GetEnv("SOURCE_DATE_EPOCH", source_date_epoch);
51
0
  if (!source_date_epoch.empty()) {
52
0
    std::istringstream iss(source_date_epoch);
53
0
    iss >> currentTimeT;
54
0
    if (iss.fail() || !iss.eof()) {
55
0
      cmSystemTools::Error("Cannot parse SOURCE_DATE_EPOCH as integer");
56
0
      exit(27);
57
0
    }
58
    // SOURCE_DATE_EPOCH has only a resolution in the seconds range
59
0
    microseconds = 0;
60
0
  }
61
0
  if (currentTimeT == static_cast<time_t>(-1)) {
62
0
    return std::string();
63
0
  }
64
65
0
  return this->CreateTimestampFromTimeT(currentTimeT, microseconds,
66
0
                                        static_cast<std::string>(formatString),
67
0
                                        utcFlag);
68
0
}
69
70
std::string cmTimestamp::FileModificationTime(char const* path,
71
                                              cm::string_view formatString,
72
                                              bool utcFlag) const
73
0
{
74
0
  std::string real_path =
75
0
    cmSystemTools::GetRealPathResolvingWindowsSubst(path);
76
77
0
  if (!cmsys::SystemTools::FileExists(real_path)) {
78
0
    return std::string();
79
0
  }
80
81
  // use libuv's implementation of stat(2) to get the file information
82
0
  time_t mtime = 0;
83
0
  uint32_t microseconds = 0;
84
0
  uv_fs_t req;
85
0
  if (uv_fs_stat(nullptr, &req, real_path.c_str(), nullptr) == 0) {
86
0
    mtime = static_cast<time_t>(req.statbuf.st_mtim.tv_sec);
87
    // tv_nsec has nanosecond resolution, but we truncate it to microsecond
88
    // resolution in order to be consistent with cmTimestamp::CurrentTime()
89
0
    microseconds = static_cast<uint32_t>(req.statbuf.st_mtim.tv_nsec / 1000);
90
0
  }
91
0
  uv_fs_req_cleanup(&req);
92
93
0
  return this->CreateTimestampFromTimeT(
94
0
    mtime, microseconds, static_cast<std::string>(formatString), utcFlag);
95
0
}
96
97
std::string cmTimestamp::CreateTimestampFromTimeT(time_t timeT,
98
                                                  std::string formatString,
99
                                                  bool utcFlag) const
100
0
{
101
0
  return this->CreateTimestampFromTimeT(timeT, 0, std::move(formatString),
102
0
                                        utcFlag);
103
0
}
104
105
std::string cmTimestamp::CreateTimestampFromTimeT(time_t timeT,
106
                                                  uint32_t const microseconds,
107
                                                  std::string formatString,
108
                                                  bool utcFlag) const
109
0
{
110
0
  if (formatString.empty()) {
111
0
    formatString = "%Y-%m-%dT%H:%M:%S";
112
0
    if (utcFlag) {
113
0
      formatString += "Z";
114
0
    }
115
0
  }
116
117
0
  struct tm timeStruct;
118
0
  memset(&timeStruct, 0, sizeof(timeStruct));
119
120
0
  struct tm* ptr = nullptr;
121
0
  if (utcFlag) {
122
0
    ptr = gmtime(&timeT);
123
0
  } else {
124
0
    ptr = localtime(&timeT);
125
0
  }
126
127
0
  if (!ptr) {
128
0
    return std::string();
129
0
  }
130
131
0
  timeStruct = *ptr;
132
133
0
  std::string result;
134
0
  for (std::string::size_type i = 0; i < formatString.size(); ++i) {
135
0
    char c1 = formatString[i];
136
0
    char c2 = (i + 1 < formatString.size()) ? formatString[i + 1]
137
0
                                            : static_cast<char>(0);
138
139
0
    if (c1 == '%' && c2 != 0) {
140
0
      result += this->AddTimestampComponent(c2, timeStruct, timeT, utcFlag,
141
0
                                            microseconds);
142
0
      ++i;
143
0
    } else {
144
0
      result += c1;
145
0
    }
146
0
  }
147
148
0
  return result;
149
0
}
150
151
time_t cmTimestamp::CreateUtcTimeTFromTm(struct tm& tm) const
152
0
{
153
#if defined(_MSC_VER) && _MSC_VER >= 1400
154
  return _mkgmtime(&tm);
155
#else
156
  // From Linux timegm() manpage.
157
158
0
  std::string tz_old;
159
0
  bool const tz_was_set = cmSystemTools::GetEnv("TZ", tz_old);
160
0
  tz_old = "TZ=" + tz_old;
161
162
  // The standard says that "TZ=" or "TZ=[UNRECOGNIZED_TZ]" means UTC.
163
  // It seems that "TZ=" does NOT work, at least under Windows
164
  // with neither MSVC nor MinGW, so let's use explicit "TZ=UTC"
165
166
0
  cmSystemTools::PutEnv("TZ=UTC");
167
168
0
  tzset();
169
170
0
  time_t result = mktime(&tm);
171
172
0
#  ifndef CMAKE_BOOTSTRAP
173
0
  if (tz_was_set) {
174
0
    cmSystemTools::PutEnv(tz_old);
175
0
  } else {
176
0
    cmSystemTools::UnsetEnv("TZ");
177
0
  }
178
#  else
179
  // No UnsetEnv during bootstrap.  This is good enough for CMake itself.
180
  cmSystemTools::PutEnv(tz_old);
181
  static_cast<void>(tz_was_set);
182
#  endif
183
184
0
  tzset();
185
186
0
  return result;
187
0
#endif
188
0
}
189
190
std::string cmTimestamp::AddTimestampComponent(
191
  char flag, struct tm& timeStruct, time_t const timeT, bool const utcFlag,
192
  uint32_t const microseconds) const
193
0
{
194
0
  std::string formatString = cmStrCat('%', flag);
195
196
0
  switch (flag) {
197
0
    case 'a':
198
0
    case 'A':
199
0
    case 'b':
200
0
    case 'B':
201
0
    case 'd':
202
0
    case 'H':
203
0
    case 'I':
204
0
    case 'j':
205
0
    case 'm':
206
0
    case 'M':
207
0
    case 'S':
208
0
    case 'U':
209
0
    case 'V':
210
0
    case 'w':
211
0
    case 'y':
212
0
    case 'Y':
213
0
    case '%':
214
0
      break;
215
0
    case 'Z':
216
0
#if defined(__GLIBC__)
217
      // 'struct tm' has the time zone, so strftime can honor UTC.
218
0
      static_cast<void>(utcFlag);
219
#else
220
      // 'struct tm' may not have the time zone, so strftime may
221
      // use local time.  Hard-code the UTC result.
222
      if (utcFlag) {
223
        return std::string("GMT");
224
      }
225
#endif
226
0
      break;
227
0
    case 'z': {
228
0
#if defined(__GLIBC__)
229
      // 'struct tm' has the time zone, so strftime can honor UTC.
230
0
      static_cast<void>(utcFlag);
231
#else
232
      // 'struct tm' may not have the time zone, so strftime may
233
      // use local time.  Hard-code the UTC result.
234
      if (utcFlag) {
235
        return std::string("+0000");
236
      }
237
#endif
238
0
#ifndef _AIX
239
0
      break;
240
#else
241
      std::string xpg_sus_old;
242
      bool const xpg_sus_was_set =
243
        cmSystemTools::GetEnv("XPG_SUS_ENV", xpg_sus_old);
244
      if (xpg_sus_was_set && xpg_sus_old == "ON") {
245
        break;
246
      }
247
      xpg_sus_old = "XPG_SUS_ENV=" + xpg_sus_old;
248
249
      // On AIX systems, %z requires XPG_SUS_ENV=ON to work as desired.
250
      cmSystemTools::PutEnv("XPG_SUS_ENV=ON");
251
      tzset();
252
253
      char buffer[16];
254
      size_t size = strftime(buffer, sizeof(buffer), "%z", &timeStruct);
255
256
#  ifndef CMAKE_BOOTSTRAP
257
      if (xpg_sus_was_set) {
258
        cmSystemTools::PutEnv(xpg_sus_old);
259
      } else {
260
        cmSystemTools::UnsetEnv("XPG_SUS_ENV");
261
      }
262
#  else
263
      // No UnsetEnv during bootstrap.  This is good enough for CMake itself.
264
      cmSystemTools::PutEnv(xpg_sus_old);
265
      static_cast<void>(xpg_sus_was_set);
266
#  endif
267
      tzset();
268
269
      return std::string(buffer, size);
270
#endif
271
0
    }
272
0
    case 's': // Seconds since UNIX epoch (midnight 1-jan-1970)
273
0
    {
274
      // Build a time_t for UNIX epoch and subtract from the input "timeT":
275
0
      struct tm tmUnixEpoch;
276
0
      memset(&tmUnixEpoch, 0, sizeof(tmUnixEpoch));
277
0
      tmUnixEpoch.tm_mday = 1;
278
0
      tmUnixEpoch.tm_year = 1970 - 1900;
279
280
0
      time_t const unixEpoch = this->CreateUtcTimeTFromTm(tmUnixEpoch);
281
0
      if (unixEpoch == -1) {
282
0
        cmSystemTools::Error(
283
0
          "Error generating UNIX epoch in string(TIMESTAMP ...) or "
284
0
          "file(TIMESTAMP ...). Please, file a bug report against CMake");
285
0
        return std::string();
286
0
      }
287
288
0
      return std::to_string(
289
0
        static_cast<int64_t>(std::difftime(timeT, unixEpoch)));
290
0
    }
291
0
    case 'f': // microseconds
292
0
    {
293
      // clip number to 6 digits and pad with leading zeros
294
0
      std::string microsecs = std::to_string(microseconds % 1000000);
295
0
      return std::string(6 - microsecs.length(), '0') + microsecs;
296
0
    }
297
0
    default: {
298
0
      return formatString;
299
0
    }
300
0
  }
301
302
0
  char buffer[16];
303
304
#ifdef __MINGW32__
305
  /* See a bug in MinGW: https://sourceforge.net/p/mingw-w64/bugs/793/. A work
306
   * around is to try to use strftime() from ucrtbase.dll. */
307
  using T = size_t(__cdecl*)(char*, size_t, char const*, const struct tm*);
308
  auto loadUcrtStrftime = []() -> T {
309
    auto handle =
310
      LoadLibraryExA("ucrtbase.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
311
    if (handle) {
312
#  pragma GCC diagnostic push
313
#  pragma GCC diagnostic ignored "-Wcast-function-type"
314
      return reinterpret_cast<T>(GetProcAddress(handle, "strftime"));
315
#  pragma GCC diagnostic pop
316
    }
317
    return nullptr;
318
  };
319
  static T ucrtStrftime = loadUcrtStrftime();
320
321
  if (ucrtStrftime) {
322
    size_t size =
323
      ucrtStrftime(buffer, sizeof(buffer), formatString.c_str(), &timeStruct);
324
    return std::string(buffer, size);
325
  }
326
#endif
327
328
0
  size_t size =
329
0
    strftime(buffer, sizeof(buffer), formatString.c_str(), &timeStruct);
330
331
0
  return std::string(buffer, size);
332
0
}