C++ Logo


Advanced search

Improve std::chrono::system_clock::to_time_t

From: Nemanja Boric <cpp_at_[hidden]>
Date: Wed, 26 Jun 2019 23:37:13 +0200

chrono::system_clock provides a to_time_t[1] method to convert the
into the std::time_t for the C interop and it is the only clock to provide
this functionality [2].

However, there's an issue with the implementation proposed in the standard:
the signature of the method is

static time_t to_time_t(const time_point& t) noexcept;

where the time_point has a concrete type
chrono::time_point<chrono::system_clock, chrono::duration<unspecified, unspecified>

On some (most) implementation, this duration has int64_t representation
with nanoseconds as
a tick period. While this allows a very narrow range around the unix
epoch - +/-292 years
it is normally not a problem since one can use different duration, and
C++20 also includes
out of the box std::chrono::sys_seconds and std::chrono::sys_days.

Where the current implementation breaks is the fact that when one pass
an instance of
std::chrono::sys_seconds to the std::chrono::system_clock::to_time_t
that object gets implicitly
converted to the object of the std::chrono::system_clock::time_point,
which although shares
the same clock and it has a higher precision, it has a much smaller
range. This in turn
leads to the overflow at this intermediate step and the result of the
function is wrong.

Here's the simple example:

   date::sys_days date = date::year(1500)/1/1;
   time_t unix_time = std::chrono::system_clock::to_time_t(date);
   std::cout << "to_time_t: " << std::ctime(&unix_time); // Fri Jul 21
01:34:33 2084

While we can't guarantee much for this conversion because std::time_t is
in the end
unspecified, we can use the best effort to minimize the error case. I
propose to replace the
existing signature of the to_time_t with something akin to:

   template <typename TimePoint>
     static std::time_t
     to_time_t(const TimePoint& __t,
         typename std::enable_if<std::is_same<typename TP::clock,
system_clock>::value>::type* = 0) noexcept

Since this doesn't specify the time_point's concrete type we will avoid the
implicit conversion and we'll simply cast __t.time_since_epoch() to seconds
in the possible implementation.

I believe this method is backward compatible and more permissive than
the original one,
while still making the same guarantee - it shoudln't allow passing the
time_points measured
on the clock different than the system one (since this clock is the only
that map time points
into the C-style time.

While using the duration_cast to seconds and count() get the value and
making sure that
the system clock is used, I see no reason why we don't improve the
current function since
it's been advertied as the entry point for C-style time conversion.


[1] https://en.cppreference.com/w/cpp/chrono/system_clock/to_time_t
[2] https://en.cppreference.com/w/cpp/chrono/system_clock
[3] http://eel.is/c++draft/time.clock.system

Received on 2019-06-26 16:39:07