Last Update:
Displaying File Time in C++: Finally fixed in C++20
Table of Contents
In this blog post, we’ll examine the potentially simple task of getting and displaying file time. Usually, we can depend on some library or OS-specific code to get that file property. Fortunately, since C++20, we have a reliable and simple technique from the std::filesystem
and std::chrono
components.
Let’s jump in.
Before C++17
In C++, working with files was usually reserved for OS APIs and third-party libraries. Here are two examples of such an approach:
Linux/POSIX Version
In Linux or any POSIX-compliant system, you can use the stat
function to get file times.
#include <iostream>
#include <fstream>
#include <sys/stat.h>
#include <ctime>
int main() {
const char* file_path = "example.txt";
std::ofstream(file_path) << "Hello, World!";
struct stat file_stat;
if (stat(file_path, &file_stat) == 0) {
std::time_t mod_time = file_stat.st_mtime;
char* str = std::asctime(std::localtime(&mod_time));
std::cout << "Last modification time: " << str;
}
else
std::cerr << "Error getting file status\n";
}
Windows/MSVC Version
In Windows, you can use the GetFileTime
function to get file times.
#include <iostream>
#include <windows.h>
#include <fstream>
#include <ctime>
void printFileTime(const FILETIME& ft) {
SYSTEMTIME st;
FileTimeToSystemTime(&ft, &st);
std::tm tm = {};
tm.tm_year = st.wYear - 1900;
tm.tm_mon = st.wMonth - 1;
tm.tm_mday = st.wDay;
tm.tm_hour = st.wHour;
tm.tm_min = st.wMinute;
tm.tm_sec = st.wSecond;
std::time_t time = std::mktime(&tm);
std::cout << "Last modification time: " << std::asctime(std::localtime(&time));
}
int main() {
const char* file_path = "example.txt";
std::ofstream(file_path) << "Hello, World!";
HANDLE hFile = CreateFile(file_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile != INVALID_HANDLE_VALUE) {
FILETIME ftCreate, ftAccess, ftWrite;
if (GetFileTime(hFile, &ftCreate, &ftAccess, &ftWrite))
printFileTime(ftWrite);
else
std::cerr << "Error getting file time\n";
CloseHandle(hFile);
}
else
std::cerr << "Error opening file\n";
}
C++17 and the std::filesystem
Finally, in C++17, we got std::filesystem
, which is based on the well-tested boost::filesystem
. Inside this package, we have the filesystem::last_write_time()
functions:
We have one free function and a member function in directory_entry
. They both return file_time_type
which in C++17 is defined as:
// C++17:
using file_time_type = std::chrono::time_point</*trivial-clock*/>;
However, this is a bit inconvenient:
From the standard, 30.10.25 Header <filesystem>
synopsis:
A> trivial-clock
is an implementation-defined type that satisfies the TrivialClock requirements and that is capable of representing and measuring file time values. Implementations should ensure that the resolution and range of file_time_type
reflect the operating system dependent resolution and range of file time values.
In other words, it’s implementation-dependent.
For example, in the GCC/Clang STL file, time is implemented on top of chrono::system_clock
, but in MSVC, it’s a platform-specific clock.
Here are some more details about the implementation decisions in Visual Studio: std::filesystem::file_time_type does not allow easy conversion to time_t
Let’s have a look at some code.
auto filetime = fs::last_write_time(myPath);
const auto toNow = fs::file_time_type::clock::now() - filetime;
const auto elapsedSec = duration_cast<seconds>(toNow).count();
// skipped std::chrono prefix for duration_cast and seconds
The above code gives you a way to compute the number of seconds since the last update. However, this is not as useful as showing a real date.
On POSIX (GCC and Clang Implementation), you can easily convert file time to system_clock
and then obtain std::time_t
:
auto filetime = fs::last_write_time(myPath);
std::time_t convfiletime = std::chrono::system_clock::to_time_t(filetime);
std::cout << "Updated: " << std::ctime(&convfiletime) << '\n';
In MSVC, the code won’t compile. However, there’s a guarantee that file_time_type
is usable with native OS functions that takes FILETIME
. So we can write the following code to solve the issue:
auto filetime = fs::last_write_time(myPath);
FILETIME ft;
memcpy(&ft, &filetime, sizeof(FILETIME)); // << !
SYSTEMTIME stSystemTime;
if (FileTimeToSystemTime(&ft, &stSystemTime)) {
// use stSystemTime.wYear, stSystemTime.wMonth, stSystemTime.wDay, ...
// see printFileTime from the example above...
}
Notice the use of memcpy
to map filetime
onto the ft
object - a form of type erasure.
C++20 - finally portable!
The situation improved, as in C++20, we got std::chrono::file_clock
and conversion routines between clocks. See P0355
// C++17
using file_time_type = std::chrono::time_point</*trivial-clock*/>;
// C++20
using file_time_type = std::chrono::time_point<std::chrono::file_clock>;
In short, as of C++20, file time is now well-defined and not implementation-specific. This allows us to write short and portable code.
Here’s a simple version of our example:
#include <iostream>
#include <fstream>
#include <filesystem>
#include <format>
#include <chrono>
int main() {
auto temp = std::filesystem::temp_directory_path() / "example.txt";
std::ofstream(temp.c_str()) << "Hello, World!";
auto ftime = std::filesystem::last_write_time(temp);
// format
std::cout << std::format("File write time is {0:%R} on {0:%F}\n", ftime);
// or <<
std::cout << ftime;
std::filesystem::remove(temp);
}
The example creates a temp directory with a temporary file and then writes some strings into it. We fetch the last write time by using the fs::last_write_time()
non-member function, which returns the time_point<file_clock>
. This time point object can be printed via std::format
or the stream operator.
Homework - sorting files by time
Using the functionality of file_clock
, can you implement a program that lists all files in a current directory sorted by date?
Here’s the starting code:
#include <iostream>
#include <fstream>
#include <filesystem>
#include <format>
#include <map>
#include <algorithm>
#include <chrono>
#include <thread>
namespace fs = std::filesystem;
int main() {
std::vector<fs::path> toDelete;
// create some files...
for (int i = 0; i < 5; ++i)
{
toDelete.emplace_back(std::format("example{}.txt", i));
std::ofstream(toDelete.back().c_str()) << "Hello, World!";
std::this_thread::sleep_for(std::chrono::seconds(1));
}
// your code here...
for (auto& f : toDelete)
fs::remove(f);
}
Start from this @Compiler Explorer example.
Here’s the expected and possible output:
01:45:59 on 2022-03-25, /app/example.cpp
01:46:05 on 2022-03-25, /app/output.s
01:46:05 on 2022-03-25, /app/compilation-result.json
01:46:05 on 2022-03-25, /app/example0.txt
01:46:06 on 2022-03-25, /app/example1.txt
01:46:07 on 2022-03-25, /app/example2.txt
01:46:08 on 2022-03-25, /app/example3.txt
01:46:09 on 2022-03-25, /app/example4.txt
Summary
C++20 is a big standard revision that brings us lots of powerful features. Fortunately, it also updates existing parts, like better definitions for file_clock
. Thanks to it, we could move from vendor-specific code into cross-platform solutions.
Back to you
- Do you use
std::filesystem
? - What common operations do you do on files through code?
Share comments below
I've prepared a valuable bonus for you!
Learn all major features of recent C++ Standards on my Reference Cards!
Check it out here: