string(TIMESTAMP): add %f specifier for microseconds
The %f specified extends the string(TIMESTAMP) and file(TIMESTAMP) commands to output the timestamp with a microsecond resolution. This convention is offered by python's datetime module. Before, the precision was limited to seconds. The implementation is done by extending existing cmTimestamp methods with a `microseconds` parameter. This parameter is optional in order to be backwards compatible. The timestamps are now received in a cross-platform manner using libuv, since the standard C functions like time() don't allow for sub-second precision. This requires libuv 1.28 or higher. We already require higher than that on Windows, so update the required version for other platforms. Implements: #19335
This commit is contained in:
parent
44939f01e7
commit
c050d6a01e
@ -655,7 +655,7 @@ macro (CMAKE_BUILD_UTILITIES)
|
|||||||
if(WIN32)
|
if(WIN32)
|
||||||
find_package(LibUV 1.38.0)
|
find_package(LibUV 1.38.0)
|
||||||
else()
|
else()
|
||||||
find_package(LibUV 1.10.0)
|
find_package(LibUV 1.28.0)
|
||||||
endif()
|
endif()
|
||||||
if(NOT LIBUV_FOUND)
|
if(NOT LIBUV_FOUND)
|
||||||
message(FATAL_ERROR
|
message(FATAL_ERROR
|
||||||
|
@ -490,6 +490,9 @@ specifiers:
|
|||||||
``%S``
|
``%S``
|
||||||
The second of the current minute. 60 represents a leap second. (00-60)
|
The second of the current minute. 60 represents a leap second. (00-60)
|
||||||
|
|
||||||
|
``%f``
|
||||||
|
The microsecond of the current second (000000-999999).
|
||||||
|
|
||||||
``%U``
|
``%U``
|
||||||
The week number of the current year (00-53).
|
The week number of the current year (00-53).
|
||||||
|
|
||||||
|
5
Help/release/dev/timestamp-microseconds.rst
Normal file
5
Help/release/dev/timestamp-microseconds.rst
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
timestamp-microseconds
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
* The :command:`string(TIMESTAMP)` and :command:`file(TIMESTAMP)` commands now
|
||||||
|
support the ``%f`` specifier for microseconds.
|
@ -17,18 +17,27 @@
|
|||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#ifdef __MINGW32__
|
#ifdef __MINGW32__
|
||||||
# include <libloaderapi.h>
|
# include <libloaderapi.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <cm3p/uv.h>
|
||||||
|
|
||||||
#include "cmStringAlgorithms.h"
|
#include "cmStringAlgorithms.h"
|
||||||
#include "cmSystemTools.h"
|
#include "cmSystemTools.h"
|
||||||
|
|
||||||
std::string cmTimestamp::CurrentTime(const std::string& formatString,
|
std::string cmTimestamp::CurrentTime(const std::string& formatString,
|
||||||
bool utcFlag) const
|
bool utcFlag) const
|
||||||
{
|
{
|
||||||
time_t currentTimeT = time(nullptr);
|
// 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;
|
std::string source_date_epoch;
|
||||||
cmSystemTools::GetEnv("SOURCE_DATE_EPOCH", source_date_epoch);
|
cmSystemTools::GetEnv("SOURCE_DATE_EPOCH", source_date_epoch);
|
||||||
if (!source_date_epoch.empty()) {
|
if (!source_date_epoch.empty()) {
|
||||||
@ -38,12 +47,15 @@ std::string cmTimestamp::CurrentTime(const std::string& formatString,
|
|||||||
cmSystemTools::Error("Cannot parse SOURCE_DATE_EPOCH as integer");
|
cmSystemTools::Error("Cannot parse SOURCE_DATE_EPOCH as integer");
|
||||||
exit(27);
|
exit(27);
|
||||||
}
|
}
|
||||||
|
// SOURCE_DATE_EPOCH has only a resolution in the seconds range
|
||||||
|
microseconds = 0;
|
||||||
}
|
}
|
||||||
if (currentTimeT == time_t(-1)) {
|
if (currentTimeT == time_t(-1)) {
|
||||||
return std::string();
|
return std::string();
|
||||||
}
|
}
|
||||||
|
|
||||||
return this->CreateTimestampFromTimeT(currentTimeT, formatString, utcFlag);
|
return this->CreateTimestampFromTimeT(currentTimeT, microseconds,
|
||||||
|
formatString, utcFlag);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string cmTimestamp::FileModificationTime(const char* path,
|
std::string cmTimestamp::FileModificationTime(const char* path,
|
||||||
@ -57,13 +69,34 @@ std::string cmTimestamp::FileModificationTime(const char* path,
|
|||||||
return std::string();
|
return std::string();
|
||||||
}
|
}
|
||||||
|
|
||||||
time_t mtime = cmsys::SystemTools::ModifiedTime(real_path);
|
// use libuv's implementation of stat(2) to get the file information
|
||||||
return this->CreateTimestampFromTimeT(mtime, formatString, utcFlag);
|
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, formatString,
|
||||||
|
utcFlag);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string cmTimestamp::CreateTimestampFromTimeT(time_t timeT,
|
std::string cmTimestamp::CreateTimestampFromTimeT(time_t timeT,
|
||||||
std::string formatString,
|
std::string formatString,
|
||||||
bool utcFlag) const
|
bool utcFlag) const
|
||||||
|
{
|
||||||
|
return this->CreateTimestampFromTimeT(timeT, 0, std::move(formatString),
|
||||||
|
utcFlag);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string cmTimestamp::CreateTimestampFromTimeT(time_t timeT,
|
||||||
|
const uint32_t microseconds,
|
||||||
|
std::string formatString,
|
||||||
|
bool utcFlag) const
|
||||||
{
|
{
|
||||||
if (formatString.empty()) {
|
if (formatString.empty()) {
|
||||||
formatString = "%Y-%m-%dT%H:%M:%S";
|
formatString = "%Y-%m-%dT%H:%M:%S";
|
||||||
@ -95,7 +128,8 @@ std::string cmTimestamp::CreateTimestampFromTimeT(time_t timeT,
|
|||||||
: static_cast<char>(0);
|
: static_cast<char>(0);
|
||||||
|
|
||||||
if (c1 == '%' && c2 != 0) {
|
if (c1 == '%' && c2 != 0) {
|
||||||
result += this->AddTimestampComponent(c2, timeStruct, timeT);
|
result +=
|
||||||
|
this->AddTimestampComponent(c2, timeStruct, timeT, microseconds);
|
||||||
++i;
|
++i;
|
||||||
} else {
|
} else {
|
||||||
result += c1;
|
result += c1;
|
||||||
@ -144,9 +178,9 @@ time_t cmTimestamp::CreateUtcTimeTFromTm(struct tm& tm) const
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string cmTimestamp::AddTimestampComponent(char flag,
|
std::string cmTimestamp::AddTimestampComponent(
|
||||||
struct tm& timeStruct,
|
char flag, struct tm& timeStruct, const time_t timeT,
|
||||||
const time_t timeT) const
|
const uint32_t microseconds) const
|
||||||
{
|
{
|
||||||
std::string formatString = cmStrCat('%', flag);
|
std::string formatString = cmStrCat('%', flag);
|
||||||
|
|
||||||
@ -180,13 +214,19 @@ std::string cmTimestamp::AddTimestampComponent(char flag,
|
|||||||
const time_t unixEpoch = this->CreateUtcTimeTFromTm(tmUnixEpoch);
|
const time_t unixEpoch = this->CreateUtcTimeTFromTm(tmUnixEpoch);
|
||||||
if (unixEpoch == -1) {
|
if (unixEpoch == -1) {
|
||||||
cmSystemTools::Error(
|
cmSystemTools::Error(
|
||||||
"Error generating UNIX epoch in "
|
"Error generating UNIX epoch in string(TIMESTAMP ...) or "
|
||||||
"STRING(TIMESTAMP ...). Please, file a bug report against CMake");
|
"file(TIMESTAMP ...). Please, file a bug report against CMake");
|
||||||
return std::string();
|
return std::string();
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::to_string(static_cast<long int>(difftime(timeT, unixEpoch)));
|
return std::to_string(static_cast<long int>(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: {
|
default: {
|
||||||
return formatString;
|
return formatString;
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include "cmConfigure.h" // IWYU pragma: keep
|
#include "cmConfigure.h" // IWYU pragma: keep
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@ -23,9 +24,14 @@ public:
|
|||||||
std::string CreateTimestampFromTimeT(time_t timeT, std::string formatString,
|
std::string CreateTimestampFromTimeT(time_t timeT, std::string formatString,
|
||||||
bool utcFlag) const;
|
bool utcFlag) const;
|
||||||
|
|
||||||
|
std::string CreateTimestampFromTimeT(time_t timeT, uint32_t microseconds,
|
||||||
|
std::string formatString,
|
||||||
|
bool utcFlag) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
time_t CreateUtcTimeTFromTm(struct tm& timeStruct) const;
|
time_t CreateUtcTimeTFromTm(struct tm& timeStruct) const;
|
||||||
|
|
||||||
std::string AddTimestampComponent(char flag, struct tm& timeStruct,
|
std::string AddTimestampComponent(char flag, struct tm& timeStruct,
|
||||||
time_t timeT) const;
|
time_t timeT,
|
||||||
|
uint32_t microseconds = 0) const;
|
||||||
};
|
};
|
||||||
|
@ -1 +1 @@
|
|||||||
RESULT=2005-08-07 23:19:49 Sunday=Sun August=Aug 05 day=219 wd=0 week=32 w_iso=31 %I=11 epoch=1123456789
|
RESULT=2005-08-07 23:19:49.000000 Sunday=Sun August=Aug 05 day=219 wd=0 week=32 w_iso=31 %I=11 epoch=1123456789
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
set(ENV{SOURCE_DATE_EPOCH} "1123456789")
|
set(ENV{SOURCE_DATE_EPOCH} "1123456789")
|
||||||
string(TIMESTAMP RESULT "%Y-%m-%d %H:%M:%S %A=%a %B=%b %y day=%j wd=%w week=%U w_iso=%V %%I=%I epoch=%s" UTC)
|
string(TIMESTAMP RESULT "%Y-%m-%d %H:%M:%S.%f %A=%a %B=%b %y day=%j wd=%w week=%U w_iso=%V %%I=%I epoch=%s" UTC)
|
||||||
message("RESULT=${RESULT}")
|
message("RESULT=${RESULT}")
|
||||||
|
Loading…
Reference in New Issue
Block a user