HuskyDucky
HuskyDucky

Reputation: 1288

How to convert std::filesystem::file_time_type to time_t?

I wrote a solution for Windows using MSVC2015 where the follow code converts the std::filesystem::last_write_time() result to time_t:

time_t ftime = std::file_time_type::clock::to_time_t(fs::last_write_time("/Path/filename"))

It works well. Then, when I tried to port the solution to Linux using gcc 9.3 (-std=C++2a), I've got the follow error:

Error: 'to_time_t' is not member of 'std::chrono::time_point<std::filesystem::_file_clock>::clock' {aka 'std::filesystem::__file_clock'}

I searched for a solution, but what I found is based on a solution included in an example of std::filesystem::last_write_time() at cplusplus.com. The solution is shown bellow:

auto ftime = fs::last_write_time(p);
std::time_t cftime = decltype(ftime)::clock::to_time_t(ftime);

Unfortunately, it doesn't work to me. Actually, the example has a comment that says it won't work in MSVC (worked at MSVC2015) or GCC 9; C++20 will allow portable output.

Now, I'm stuck. How can I make this conversion using gcc?

Upvotes: 22

Views: 20904

Answers (9)

Maxim Kozak
Maxim Kozak

Reputation: 11

I was able to solve it myself with

time_t last_write_time = std::chrono::system_clock::to_time_t(std::chrono::utc_clock::to_sys(std::chrono::file_clock::to_utc(std::filesystem::last_write_time("C:test.txt"))));

Upvotes: 0

As per https://libcxx.llvm.org/DesignDocs/FileTimeType.html

file_time_type tp = last_write_time(p);
time_t secs = duration_cast<seconds>(tp.time_since_epoch()).count();

Tested compiling on gcc 9.5

Upvotes: 0

redleg
redleg

Reputation: 317

This is not a direct answer to the question, and maybe not a portable solution, but I got a similar problem of converting a std::filesystem::file_time_type to a std::chrono::system_clock::time_point (that can easily be converted to a time_t) in Visual Studio 2017. This worked for me, and may help others who are also on the search:

using namespace std::chrono;

// a dummy file_time_type to work with
const auto ftp = std::filesystem::file_time_type::clock::now();

// get the ticks and subtract the file time epoch adjustment
const auto ticks = ftp.time_since_epoch().count() - __std_fs_file_time_epoch_adjustment;

// create a time_point from ticks
const auto tp = system_clock::time_point(system_clock::time_point::duration(ticks));

// finaly convert to time_t
std::time_t tt = system_clock::time_point::clock::to_time_t(tp);

Upvotes: 0

Akorn Farmer
Akorn Farmer

Reputation: 1

Here is direct conversion from std::filesystem::file_time_type to time_t that worked in C++20 standard on MS Visual C++ 2022, Clang, and gcc as of Dec. 2022.

// ....
#include <filesystem>
#include <chrono>

using namespace std;

// ....
    filesystem::file_time_type  myfmtime =
        filesystem::last_write_time("myfile.txt");

    // file_clock::file_time_type -> utc_clock::time_point
    // -> system_clock::time_point -> time_t

    chrono::time_point<chrono::system_clock> ftsys;

#ifdef  __GNUC__
        ftsys = chrono::file_clock::to_sys(myfmtime);
#else
        // MSVC, CLANG
        ftsys = chrono::utc_clock::to_sys(
                chrono::file_clock::to_utc(myfmtime));
#endif

    time_t ftt = chrono::system_clock::to_time_t(ftsys);

    char chrbuf[30];
    struct tm ftm; ftm = {0};
    localtime_s(&ftm, &ftt);
    strftime(chrbuf, 30, "%F %T", &ftm);

There must be some standard direct conversion routine to convert file time to time_t and vice versa without using system clock type.

Upvotes: 0

korri123
korri123

Reputation: 499

C++20 solution

const auto fileTime = std::filesystem::last_write_time(filePath);
const auto systemTime = std::chrono::clock_cast<std::chrono::system_clock>(fileTime);
const auto time = std::chrono::system_clock::to_time_t(systemTime);

Upvotes: 19

tumnus
tumnus

Reputation: 11

Another not direct solution: If you happen to be using boost...

    inline std::string filestamp(const std::string& path) {
        std::time_t t = boost::filesystem::last_write_time(path);
        return std::asctime(std::localtime(&t));
    }

Upvotes: 1

TechnoRecluse
TechnoRecluse

Reputation: 51

After some digging I managed to rewrite the example they give and it seems to work; the key being converting to system_clock then to time_t.

#include <iostream>
#include <chrono>
#include <iomanip>
#include <fstream>
#include <filesystem>
namespace fs = std::filesystem;
using namespace std::chrono_literals;
int main()
{
  fs::path p = fs::current_path() / "example.bin";
  std::ofstream(p.c_str()).put('a'); // create file
  auto ftime = fs::last_write_time(p);
  auto cftime = std::chrono::system_clock::to_time_t(std::chrono::file_clock::to_sys(ftime));

  std::cout << "File write time is " << std::asctime(std::localtime(&cftime)) << '\n';

  fs::last_write_time(p, ftime + 1h); // move file write time 1 hour to the future
  ftime = fs::last_write_time(p);     // read back from the filesystem

  cftime = std::chrono::system_clock::to_time_t(std::chrono::file_clock::to_sys(ftime));
  std::cout << "File write time is " << std::asctime(std::localtime(&cftime)) << '\n';
  fs::remove(p);
}

Upvotes: 5

Gulrak
Gulrak

Reputation: 816

As already said, there is no perfect way to do that in C++17. Depending on the actual use-case it might be good enough to use a portable approximation. Based on my answer to "How to convert std::filesystem::file_time_type to a string using GCC 9", I want to suggest the helper function used there:

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);
}

Be aware, that it uses a call to now() on each clock, so it is not an exact, round-trip-guaranteed solution, but it might be usable for you, until the gap in the library is closed. It is based on the fact that difference between time points of the same clock is easy and there is an operator+ for duration and time_point of different sources.

For ways to lower the risk of a relevant error even more, I want to point to the conversion between C++11 clocks where some statistical analysis was made with ideas to mitigate possible errors, but when acceptable, I just use the code above.

Upvotes: 24

Nicol Bolas
Nicol Bolas

Reputation: 474326

Before C++20? There is no portable way to do that. It worked in that version of Visual Studio because that version of VS's filesystem implementation just so happens to use system_clock for the filesystem clock type. This is not required by the C++ standard, but it is allowed. So your code just so happened to work.

Pre-C++20, there was no mechanism to align two clocks together, so that time from one could be converted into the time of another. So if the filesystem clock isn't system_clock, you're out of luck. You would have to write implementation-specific code using knowledge of how that implementation implemented their filesystem clock (basically knowing what epoch it uses) so that you could manually convert it to a system_clock::time_point.

C++20 gives system_clock a fixed epoch to be used across all implementations (UNIX-time), and requires that file_clock be able to convert its time points into system_clock time points.

Upvotes: 7

Related Questions