Drise
Drise

Reputation: 4388

Cannot convert between std::chrono::time_point s

Why won't the following function compile, with the error

cannot convert from 'std::chrono::time_point<std::chrono::steady_clock,std::chrono::duration<double,std::nano>>' to 'std::chrono::time_point<std::chrono::steady_clock,std::chrono::steady_clock::duration>'

#include <chrono>

typedef std::chrono::high_resolution_clock::time_point TimePoint;
typedef std::chrono::duration<double, std::ratio<86400>> JulianDays;

TimePoint JulianDaysToUTC(const JulianDays& days)
{
    static const JulianDays EquivalentJulianYearInDays(2451545.0);
    static const JulianDays LeapSecondCorrection(0.0008);
    static const TimePoint CorrectedEpoch = TimePoint() - EquivalentJulianYearInDays + LeapSecondCorrection;
    return CorrectedEpoch + days;
}

Note: TimePoint() was substituted for a function returning a TimePoint, but that return value is not / should not be relevant.

Modifying it to use an integer duration allows it to compile, but I lose the fractional day portion, which is undesired.

#include <chrono>

typedef std::chrono::high_resolution_clock::time_point TimePoint;
typedef std::chrono::duration<int, std::ratio<86400>> Days;
typedef std::chrono::duration<double, std::ratio<86400>> JulianDays;

TimePoint JulianDaysToUTC(const JulianDays& days)
{
    using std::chrono::duration_cast;
    static const JulianDays EquivalentJulianYearInDays(2451545.0);
    static const JulianDays LeapSecondCorrection(0.0008);
    static const TimePoint CorrectedEpoch = TimePoint() - duration_cast<Days>(EquivalentJulianYearInDays) + duration_cast<Days>(LeapSecondCorrection);
    return CorrectedEpoch + duration_cast<Days>(days);
}

Upvotes: 3

Views: 2910

Answers (1)

Howard Hinnant
Howard Hinnant

Reputation: 218900

The <chrono> library is designed so that truncation error won't happen implicitly. This is because truncation error can easily happen and often results in an accidental loss of information.

The error message:

cannot convert from time_point<steady_clock, duration<double,std::nano>> to time_point<steady_clock, steady_clock::duration>

is saying that an implicit conversion from fractional nanoseconds (double-based) to whole nanoseconds (integral-based) has been attempted but is not allowed. It turns out that every steady_clock::duration happens to be nanoseconds, though that is not specified.

If truncation is what you desire (as in this case), one can use duration_cast or time_point_cast to truncate towards zero. In C++17, floor, ceil and round truncation modes are added.

Here is the most straight-forward way to perform the truncation casts that the library is refusing to do implicitly:

#include <chrono>

typedef std::chrono::high_resolution_clock::time_point TimePoint;
typedef std::chrono::duration<double, std::ratio<86400>> JulianDays;

TimePoint JulianDaysToUTC(const JulianDays& days)
{
    static const JulianDays EquivalentJulianYearInDays(2451545.0);
    static const JulianDays LeapSecondCorrection(0.0008);
    static const TimePoint CorrectedEpoch =
        std::chrono::time_point_cast<TimePoint::duration>(
            TimePoint() - EquivalentJulianYearInDays + LeapSecondCorrection);
    return std::chrono::time_point_cast<TimePoint::duration>(CorrectedEpoch + days);
}

The first cast is necessary because the expression TimePoint() - EquivalentJulianYearInDays + LeapSecondCorrection has type time_point<high_resolution_clock, duration<double, nano>> (floating point nanoseconds time_point), and the destination type is a integral-based nanoseconds time_point. Ditto for the second conversion.

auto could be used to clean this code up a little, and avoid one of the conversions:

#include <chrono>

typedef std::chrono::high_resolution_clock::time_point TimePoint;
typedef std::chrono::duration<double, std::ratio<86400>> JulianDays;

TimePoint JulianDaysToUTC(const JulianDays& days)
{
    static const JulianDays EquivalentJulianYearInDays(2451545.0);
    static const JulianDays LeapSecondCorrection(0.0008);
    static const auto CorrectedEpoch = TimePoint() -
                                       EquivalentJulianYearInDays + LeapSecondCorrection;
    return std::chrono::time_point_cast<TimePoint::duration>(CorrectedEpoch + days);
}

Now CorrectedEpoch is a double-based nanosecond time_point, but that detail is not really important to your algorithm.


Also, Nicol Bolas's comment about the suspicious use of high_resolution_clock is warranted. Your code may work if you never mix a TimePoint with a high_resolution_clock::time_point that came from high_resolution_clock::now(). However you would be safer to just create your own custom clock with a documented 2000-01-01 12:00:00 UTC epoch. Then any accidental mixing would be caught at compile time.

Upvotes: 3

Related Questions