I'm using HowardHinnant/date in lieu of the new C++20 calendar/timezone facilities that are not yet available in Clang/GCC. My question applies equally to both implementations: How do I safely clock_cast
time_point
s having days
duration?
When I try:
using namespace date; // or using std::chrono in C++20
tai_time<days> tai{days{42}};
sys_days sys = clock_cast<std::chrono::system_clock>(tai);
I get a "no viable conversion" error for the last statement. It turns out the result uses a duration that is common_type<days, seconds>
, which comes from utc_clock::to_sys()
that's used in the conversion. The common duration type is std::chrono::seconds
, so it's normal that it can't be directly converted to a days
duration.
I can get it to compile if I use an explicit duration_cast
:
using namespace date; // or using std::chrono in C++20
tai_time<days> tai{days{42}};
auto casted = clock_cast<std::chrono::system_clock>(tai);
sys_days sys{std::chrono::duration_cast<days>(casted.time_since_epoch())};
... but I'm worried that the result might by off by a day due to the truncation (especially for dates preceding the epoch). Is this the correct way to do what I'm trying to do? Should I be using floor
instead of duration_cast
?
Why is there even a std::common_type_t<Duration, std::chrono::seconds>
in utc_clock::to_sys()
anyway? Shouldn't it simply return the same duration type?
The reason that the clock_cast
insists on at least seconds
precision is because the offset between the epochs of system_clock
and tai_clock
has a precision of seconds
:
auto diff = sys_days{} - clock_cast<system_clock>(tai_time<days>{});
cout << diff << " == " << duration<double, days::period>{diff} << '\n';
Output:
378691210s == 4383.000116d
So a days
precision cast would be lossy. Here's another way of looking at it:
cout << clock_cast<tai_clock>(sys_days{2021_y/June/1}) << '\n';
Output:
2021-06-01 00:00:37
I.e. TAI is currently 37 seconds ahead of system_clock
in calendrical terms.
If all you want is the date, I recommend round<days>(result)
:
cout << round<days>(clock_cast<tai_clock>(sys_days{2021_y/June/1})) << '\n';
Output:
2021-06-01 00:00:00
One could conceivably use floor
in one direction and ceil
in the other, but that would be very error prone. round
will do fine since the offset from an integral number of days is currently only 37 seconds and growing quite slowly.