Reputation: 1112
I'm converting some code over from boost::filesystem
to std::filesystem
. Previous code used boost::filesystem::last_write_time()
which returns a time_t
so direct comparison to a time_t
object I already held was trivial. By the way, this time_t
I hold is read from file contents persisted long ago, so I'm stuck with using this "time since unix epoch" type.
std::filesystem::last_write_time
returns a std::filesystem::file_time_type
. Is there a portable way to convert a file_time_type
to a time_t
, or otherwise portably compare the two objects?
#include <ctime>
#include <filesystem>
std::time_t GetATimeInSecondsSince1970Epoch()
{
return 1207609200; // Some time in April 2008 (just an example!)
}
int main()
{
const std::time_t time = GetATimeInSecondsSince1970Epoch();
const auto lastWriteTime = std::filesystem::last_write_time("c:\\file.txt");
// How to portably compare time and lastWriteTime?
}
EDIT: Please note that the sample code at cppreference.com for last_write_time
states that it's assuming the clock is a std::chrono::system_clock
which implements the to_time_t
function. This assumption is not always going to be true and isn't on my platform (VS2017).
Upvotes: 16
Views: 11312
Reputation: 1
This is my polyfill which works on Windows, MacOS and Linux. Unfortunately neither Clang nor GCC have file_clock support yet and support on Windows is also bound to recent Windows 10+ versions. See: https://github.com/microsoft/STL/issues/1911
// Copied from Boost: filesystem/src/operations.cpp
// these constants come from inspecting some Microsoft sample code
#ifdef _WIN32
inline std::time_t to_time_t(FILETIME const& ft) BOOST_NOEXCEPT {
uint64_t t = (static_cast<uint64_t>(ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
t -= 116444736000000000ull;
t /= 10000000u;
return static_cast<std::time_t>(t);
}
#else
// Copied from Stackoverflow
template<typename TP>
std::time_t to_time_t(TP tp) {
using namespace std::chrono;
auto sctp = time_point_cast<system_clock::duration>(tp - TP::clock::now() + system_clock::now());
return system_clock::to_time_t(sctp);
}
#endif
// Use this polyfill until everyone supports C++20
// This code is highly depended on the implementation
time_t PortableLastWriteTime(const std::string& File) {
#ifdef _WIN32
// We cannot use the C++20 and filesystem due to this incompatibility/error: https://github.com/microsoft/STL/issues/1911
// This problem makes this request to fail on Windows older than Windows 10 (1903) and somehow Server 2019 completely
HANDLE handle = CreateFile(File.c_str(), GENERIC_READ, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, NULL);
FILETIME lwt;
if (!::GetFileTime(handle, NULL, NULL, &lwt)) {
CloseHandle(handle);
return 0;
}
CloseHandle(handle);
return to_time_t(lwt);
#elif __GLIBCXX__
auto ftime = std::filesystem::last_write_time(File);
return to_time_t(ftime);
#elif _LIBCPP_VERSION
auto ftime = std::filesystem::last_write_time(File);
return decltype(ftime)::clock::to_time_t(ftime);
#elif __CORRECT_CPP20_VERSION
// This is the correct C++20 usage when compilers have full compatibility
const auto fileTime = std::filesystem::last_write_time(File);
const auto systemTime = std::chrono::clock_cast<std::chrono::system_clock>(fileTime);
const auto time = std::chrono::system_clock::to_time_t(systemTime);
return time;
#else
Unsupported compiler !
#endif
}
Upvotes: 0
Reputation: 521
I faced the same problem and I solved it using a dedicated code for Visual Studio.
In case of VS, I use the _wstati64
function (w
for wide char, because Windows encodes Unicode paths in Utf16) and the wstring
convertion of the path
class.
The whole thing is gathered in this function:
#if defined ( _WIN32 )
#include <sys/stat.h>
#endif
std::time_t GetFileWriteTime ( const std::filesystem::path& filename )
{
#if defined ( _WIN32 )
{
struct _stat64 fileInfo;
if ( _wstati64 ( filename.wstring ().c_str (), &fileInfo ) != 0 )
{
throw std::runtime_error ( "Failed to get last write time." );
}
return fileInfo.st_mtime;
}
#else
{
auto fsTime = std::filesystem::last_write_time ( filename );
return decltype ( fsTime )::clock::to_time_t ( fsTime );
}
#endif
}
Upvotes: 7
Reputation: 218700
Fwiw, when C++20 gets here, the portable solution will be:
clock_cast<file_clock>(system_clock::from_time_t(time)) < lastWriteTime
This converts the time_t
into file_time
as opposed to vice-versa. The advantage of this approach is that file_time
typically has a higher precision than time_t
. Converting file_time
to time_t
will loose that precision during the conversion, and thus risk making the comparison inaccurate.
Upvotes: 15
Reputation: 62553
The very article you linked shows how to do this: through to_time_t
member of the corresponding clock
of the file_time_type
.
Copy-paste from your own link:
auto ftime = fs::last_write_time(p);
std::time_t cftime = decltype(ftime)::clock::to_time_t(ftime);
If your platform doesn't give you system_clock
as a clock for file_time_type
, than there would be no portable solution (until, at least, C++20 when file_time_type
clock is standardized). Until than, you'd have to figure out what clock it actually is and than cast time appropriately through duration_cast
and friends.
Upvotes: 7