Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to convert `std::filesystem::file_time_type` to a string using GCC 9

I'm trying to format the modification time of a file as a string (UTC). The following code compiles with GCC 8, but not GCC 9.

#include <chrono>
#include <filesystem>
#include <iomanip>
#include <iostream>
#include <sstream>

namespace fs = std::filesystem;

int main() {
    fs::file_time_type file_time = fs::last_write_time(__FILE__);
    std::time_t tt = decltype(file_time)::clock::to_time_t(file_time);
    std::tm *gmt = std::gmtime(&tt);
    std::stringstream buffer;
    buffer << std::put_time(gmt, "%A, %d %B %Y %H:%M");
    std::string formattedFileTime = buffer.str();
    std::cout << formattedFileTime << '\n';
}

I tried both decltype(file_time)::clock and std::chrono::file_clock, using both C++17 and C++ 20, but neither of them worked.

$ g++-9 -std=c++2a -Wall -Wextra -lstdc++fs file_time_type.cpp

file_time_type.cpp: In function ‘int main()’:
file_time_type.cpp:12:50: error: ‘to_time_t’ is not a member of ‘std::chrono::time_point<std::filesystem::__file_clock>::clock’ {aka ‘std::filesystem::__file_clock’}
   12 |     std::time_t tt = decltype(file_time)::clock::to_time_t(file_time);
      |                                                  ^~~~~~~~~

The example on https://en.cppreference.com/w/cpp/filesystem/file_time_type mentions that it doesn't work on GCC 9, because C++20 will allow portable output, but I have no idea how to get it working. Shouldn't it work with GCC 9 if I just not use C++20?

I would prefer a C++17 solution with GCC 9, if possible.

like image 721
tttapa Avatar asked Jun 27 '19 10:06

tttapa


1 Answers

As system_clock has to_time_t, the easiest way is to convert to it. This is not perfect (due to precision issues), but most of the time good enough and what I'm using on MSVC as well:

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

Update: As I'm not yet able to comment, some clarification on why this works and what can be a problem: It works because the difference between two time points of the same clock is easy, and for the second part, there is an extra template operator+ for duration and time point of different sources (2) and ratio differences will be taken care of in std::common_type.

The remaining issue is, that the two calls to now() are not at the same time, there is a slight risk of introducing a small conversion error due to the time difference between that calls. There is another question about C++11 clock conversions that goes into much more detail on error probabilities and tricks to reduce the error, but if you don't need a round-trip conversion with comparing results, but just want to format a time stamp, this smaller solution should be good enough.

So to complete the answer to the original question:

#include <chrono>
#include <filesystem>
#include <iomanip>
#include <iostream>
#include <sstream>

namespace fs = std::filesystem;

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

int main() {
    fs::file_time_type file_time = fs::last_write_time(__FILE__);
    std::time_t tt = to_time_t(file_time);
    std::tm *gmt = std::gmtime(&tt);
    std::stringstream buffer;
    buffer << std::put_time(gmt, "%A, %d %B %Y %H:%M");
    std::string formattedFileTime = buffer.str();
    std::cout << formattedFileTime << '\n';
}
like image 68
Gulrak Avatar answered Oct 31 '22 23:10

Gulrak