The traditional Win32 data structure for holding a date/time is the FILETIME
. How do you interoperate between this and the C++ clocks like std::system_clock
and winrt::clock
?
When C++ introduced the concept of clocks in C++11, it did not require clocks to have any particular epoch. In C++20, the rules were strengthened to require system_
to have an epoch of January 1, 1970 (so-called Unix Time). The period of the system clock remains unspecified, although you can inspect it at compile time via std::
.
So how do you get a FILETIME
into a C++ clock?
C++/WinRT’s winrt::clock
comes to the rescue. This is the projection of the Windows Runtime DateTime
structure, which is defined to be in the same units as FILETIME
. Therefore, once you pack the two parts of the FILETIME
into a single int64_t
, the conversion between FILETIME
and winrt::clock
is mathematically trivial.
It’s mathematically trivial, but you still have to do a bunch of typing:
winrt::clock::time_point winrt_clock_from_FILETIME(FILETIME ft) { // First, pack it into a 64-bit integer auto count = (static_cast<long long>(ft.dwHighDateTime) << 32) | ft.dwLowDateTime; // Then place it inside a duration (aka TimeSpan) to represent / time since epoch auto span = winrt::clock::duration(count); // Alternatively: auto span = winrt::Windows::Foundation::TimeSpan(count); // Then create a winrt::clock::time_point from it return winrt::clock::time_point(span); // Alternatively: return winrt::Windows::Foundation::DateTime(span); }
Fortunately, C++/WinRT comes with helpers to make this easier.
For example, the winrt::
structure lets you convert between FILETIME
and 64-bit integers.
winrt::clock::time_point winrt_clock_from_FILETIME(FILETIME ft)
{
// First, pack it into a 64-bit integer
auto count = winrt::file_time(ft).value;
// Then place it inside a duration (aka TimeSpan) to represent
/ time since epoch
auto span = winrt::clock::duration(count);
// Alternatively: auto span = winrt::Windows::Foundation::TimeSpan(count);
// Then create a winrt::clock::time_point from it
return winrt::clock::time_point(span);
// Alternatively: return winrt::Windows::Foundation::DateTime(span);
}
And the conversion to a duration, and then to a time point is handled by another helper:
winrt::clock::time_point winrt_clock_from_FILETIME(FILETIME ft) { auto t = winrt::file_time(ft); return winrt::clock::from_file_time(t); }
And the entire thing is handled by yet another helper:
winrt::clock::time_point winrt_clock_from_FILETIME(FILETIME ft) { return winrt::clock::from_FILETIME(ft); }
So we didn’t need to write our function at all. C++/WinRT came with it built-in.
Once you’ve entered C++-land, you can use a clock cast to convert it to other C++ clocks.
template<typename Clock> typename Clock::time_point clock_from_FILETIME(FILETIME ft) { return std::chrono::clock_cast<Clock>( winrt::clock::from_FILETIME(ft)); } template<typename TimePoint> FILETIME FILETIME_from_clock(TimePoint const& t) { return winrt::clock::to_FILETIME( std::chrono::clock_cast<winrt::clock>(t)); }
Bonus reading: Converting between Windows FILETIME and Unix time_t without having to type the magic number 116444736000000000. The winrt::clock
also gets you out of the constants business.
magic1 = winrt::clock::from_time_t(0).time_since_epoch().count(); magic2 = winrt::clock::period::num / winrt::clock::period::den;
Â
Or am I missing something?
Note: the MSVC implementation of this won’t work on Server 2019, as icu.dll is missing on that installation. Somehow this is not regarded as a bug.