I need to convert C++ std::chrono::steady_clock::time_point
to C++/CLI System::DateTime
.
Background:
I am wrapping a C++ library with a C++/CLI interface, to be used by a .NET app.
One of the C++ methods return a std::chrono::steady_clock::time_point
.
I thought it is appropriate to returns a System::DateTime
from the C++/CLI wrapper method.
Thus the need to convert.
I am aware that if I had a system_clock::time_point
, I could have converted it to time_t
as explained here. Then I could have used DateTimeOffset.FromUnixTimeMilliseconds
, and from it get a System::DateTime
.
Another approach could have been to use time_since_epoch
.
But neither to_time_t
nor time_since_epoch
are available for std::chrono::steady_clock
(about time_since_epoch
see here).
However - I cannot change the C++ interface.
Also didn't manage to properly convert steady_clock::time_point
to e.g. system_clock::time_point
.
The solution I came up with:
I take current time from both std::chrono::steady_clock
and System::DateTime
,
then calculate offset from the steady_clock::time_point
,
and finally apply this offset in reverse to the DateTime time.
I calculate the offset in milliseconds, and since the precision I am interested in is of seconds, it works well.
This method is shown in the code below.
But it feels a bit awkward. It is also sensitive to the requested precision.
My question: can you suggest a better way to do the conversion ?
using namespace System;
#include <chrono>
System::DateTime SteadyClockTimePointToDateTime(std::chrono::steady_clock::time_point const & tCPP)
{
auto nowCPP = std::chrono::steady_clock::now();
auto nowCLI = System::DateTime::Now;
long long milliSecsSinceT = std::chrono::duration_cast<std::chrono::milliseconds>(nowCPP - tCPP).count();
System::DateTime tCLI = nowCLI - System::TimeSpan::FromMilliseconds(static_cast<double>(milliSecsSinceT));
return tCLI;
}
int main(array<System::String ^> ^args)
{
System::Console::WriteLine("System::DateTime::Now (for debug): " + System::DateTime::Now.ToString()); // print reference time for debug
auto tCPP = std::chrono::steady_clock::now(); // got the actual value from a C++ lib.
System::Threading::Thread::Sleep(100); // pass some time to simulate stuff that was executed since the time_point was received.
System::DateTime tCLI = SteadyClockTimePointToDateTime(tCPP);
System::Console::WriteLine("System::DateTime (converted): " + tCLI.ToString()); // should show a time very close to System::DateTime::Now above
return 0;
}
Output example:
System::DateTime::Now (for debug): 23-May-22 16:41:04
System::DateTime (converted): 23-May-22 16:41:04
Note: I added the C++ tag because the question is not a pure C++/CLI issue.
E.g. there might be a solution involving conversion between std::chrono
clocks that will enable an easy further conversion to System::DateTime
(as mentioned above regarding DateTimeOffset.FromUnixTimeMilliseconds
).
The approach is sound, but the code can be made shorter and easier to read with a couple small changes:
template<typename Rep, typename Period>
System::TimeSpan DurationToTimeSpan(std::chrono::duration<Rep, Period> const& input)
{
auto milliSecs =
std::chrono::duration_cast<std::chrono::milliseconds>(input).count();
return System::TimeSpan::FromMilliseconds(milliSecs);
}
System::DateTime SteadyClockTimePointToDateTime(
std::chrono::steady_clock::time_point const & tCPP)
{
auto const nowCPP = std::chrono::steady_clock::now();
auto nowCLI = System::DateTime::Now;
auto tCLI = nowCLI + DurationToTimeSpan(tCPP - nowCPP);
}
The specific changes made are:
TimeSpan
factory function invocation in another helper function.1nowCPP
and tCPP
, to avoid having to reverse the sign later.const
to local variables having native type and which will not be changed. .NET types sadly are not const-correct, because const
-ness is only respected by the C++/CLI compiler and not the languages in which the .NET library is written.count()
and the parameter of AddMilliseconds
. If for some platform there is a different duration representation and implicit conversion doesn't work, it's better to have the compiler tell the maintenance programmer.Note that the result of this function does NOT provide the "steady clock" guarantee. In order to do so, one should generate a single time_point
/DateTime
pair and save it for later reuse.
1 Another choice would be to use the AddMilliseconds
member function, which replaces a call to TimeSpan
factory function and overloaded operator-
:
System::DateTime SteadyClockTimePointToDateTime(
std::chrono::steady_clock::time_point const & tCPP)
{
auto const nowCPP = std::chrono::steady_clock::now();
auto nowCLI = System::DateTime::Now;
auto const milliSecsUntilT =
std::chrono::duration_cast<std::chrono::milliseconds>(tCPP - nowCPP).count();
auto tCLI = nowCLI.AddMilliseconds(milliSecsUntilT);
return tCLI;
}