Micka
Micka

Reputation: 20130

Convert high_resolution_clock::time_point to time_t with VC141

In Visual Studio 2013 I just used

#include <chrono>
#include <ctime>
#include <iostream>
#include <iomanip>
#include <sstream>

std::string Time_Point_String(const std::chrono::high_resolution_clock::time_point & timePoint)
{
    time_t timeNow = std::chrono::system_clock::to_time_t(timePoint);

    tm time = *localtime(&timeNow);

    std::stringstream timeString;
timeString << std::setfill('0') << 1900 + time.tm_year << "-" << std::setw(2) << time.tm_mon + 1 << "-" << std::setw(2) << time.tm_mday << " " << std::setw(2) << time.tm_hour << ":" << std::setw(2) << time.tm_min << ":" << std::setw(2) << time.tm_sec;

    return timeString.str();
}

int main()
{
    const std::chrono::high_resolution_clock::time_point & timePoint = std::chrono::high_resolution_clock::now();

    std::cout << Time_Point_String(timePoint);
    return 0;
}

But with Visual Studio 2017 I get a compiler error:

Error C2664 '__time64_t std::chrono::system_clock::to_time_t(const std::chrono::system_clock::time_point &) noexcept': cannot convert argument 1 from 'const std::chrono::steady_clock::time_point' to 'const std::chrono::system_clock::time_point &'

So it isn't possible anymore to convert a high_resolution_clock::time_point to a different time_point like system_clock::time_point and there is no possibility to convert high_resolution_clock::time_point to time_t directly?

How can I handle this situation? Is it possible at all (some SO postings say they are just completely different clocks and conversion doesn't make sense)? As far as I've seen, the function did what I expected it to do in Visual Studio 2013 application, it has provided the right local time for a high_resolution time_point.

Upvotes: 1

Views: 4764

Answers (3)

SuperJoe5150
SuperJoe5150

Reputation: 21

It's all just data. The c++ type system is powerful, but not absolute. The reason you can convert between std::chrono::system_clock and a time_t is that they share the same epoch, or "time when the clock began". For time_t this is in seconds since the epoch "Wed Dec 31 19:00:00 1969" or since since 00:00, Jan 1 1970 UTC, although it is not defined. So converting a system clock is as simple as casting the clocks native resolution to the number of seconds since the epoch. A time_point encapsulates it's time since the epoch. For a high_resolution_clock, or any other clock other than system_clock, the epoch is not guaranteed to be the same. It may be when the computer started up, or when the cmos battery was plugged in, when the program began... Who knows? Who cares? The difference in epochs is why they are not compatible. Not necessarily their type. Although..., an alias would ensure the share the same epoch. So the difference between epochs for any given time point, applied as an offset in seconds, will give us our conversion. Without any more warm up here is the code :

#include <iostream>
#include <chrono>
#include <ctime>

namespace joe {

    template<typename Test, template<typename...> class Ref>
    struct is_specialization : public std::false_type {};
    template<template<typename...> class Ref, typename... Args>
    struct is_specialization<Ref<Args...>, Ref> : public std::true_type {};

    template <class Duration >
    using is_duration = is_specialization<Duration, std::chrono::duration>;

    template<class, class = void>
    struct is_clock : public std::false_type {};
    template<class Clock>
    struct is_clock<Clock, std::void_t<typename Clock::rep, typename Clock::period, typename Clock::duration,
        typename Clock::time_point, decltype(Clock::is_steady), decltype(Clock::now())>> : public std::true_type {};

    template<typename Clock, class Duration = typename Clock::duration>
    [[nodiscard]] static inline time_t to_time_t(const std::chrono::time_point<Clock, Duration>& time) noexcept {
        static_assert(joe::is_clock<Clock>::value, "The given type for Clock does not fulfill the requirements of a clock.");
        static_assert(joe::is_duration<Duration>::value, "The given type for Duration is not of std::chrono::duration.");
        static const long long offset = (std::is_same< Clock, std::chrono::system_clock>::value) ? 0
            : (std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count()
                - std::chrono::duration_cast<std::chrono::seconds>(Clock::now().time_since_epoch()).count());
        return std::chrono::duration_cast<std::chrono::seconds>(time.time_since_epoch()).count() + offset;
    }

    template<typename Clock, class Duration = typename Clock::duration>
    [[nodiscard]] static inline std::chrono::time_point<Clock, Duration> from_time_t( time_t time) noexcept {
        static_assert(joe::is_clock<Clock>::value, "The given type for Clock does not fulfill the requirements of a clock.");
        static_assert(joe::is_duration<Duration>::value, "The given type for Duration is not of std::chrono::duration.");
        static const long long offset = (std::is_same< Clock, std::chrono::system_clock>::value) ? 0
            : (std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count()
                - std::chrono::duration_cast<std::chrono::seconds>(Clock::now().time_since_epoch()).count());
        return std::chrono::time_point<Clock, Duration>{ std::chrono::duration_cast<Duration>(std::chrono::seconds{ time - offset }) };
    }

    [[nodiscard]] static inline std::string ctime( time_t result) noexcept
    {
        char str[26];
        ctime_s(str, sizeof str, &result);
        return std::string(str);
    }
}

void check_system_epoch()
{
    const std::chrono::system_clock::time_point timePoint;
    time_t time = joe::to_time_t(timePoint);
    std::cout << "system clock epoch : " << joe::ctime(time) << std::endl;
}

void check_high_res_epoch()
{
    const std::chrono::high_resolution_clock::time_point timePoint;
    time_t time = joe::to_time_t(timePoint);
    std::cout << "high resolution clock epoch : " << joe::ctime(time) << std::endl;
}

int main()
{
    const std::chrono::high_resolution_clock::time_point & timePoint = std::chrono::high_resolution_clock::now();

    time_t time = joe::to_time_t(timePoint);
    std::cout << joe::ctime( time ) << std::endl;

    std::cout << "Press the enter key to continue!\n";
    check_system_epoch();
    std::cin.ignore();
    std::cout << "Press the enter key to continue!\n";
    check_high_res_epoch();
    std::cin.ignore();
}

` This gives you to_time_t, from_time_t, and a ctime, that will work with any clock.

Upvotes: 2

Alan Birtles
Alan Birtles

Reputation: 36379

You can only convert between time_points from the same clock. std::chrono::high_resolution_clock is an alias for another clock. If that happens to be std::chrono::system_clock then you can convert to time_t.

If std::chrono::high_resolution_clock is another clock you can approximately convert it to std::chrono::system_clock by taking the difference between the current time and the input time in std::chrono::high_resolution_clock and adding that difference to the system time:

#include <iostream>
#include <chrono>

int main()
{
  auto input = std::chrono::high_resolution_clock::now();

  auto highResNow = std::chrono::high_resolution_clock::now();
  auto systemNow = std::chrono::system_clock::now();
  auto output = systemNow + (highResNow - input);
  std::cout << std::chrono::system_clock::to_time_t( output ) << "\n";
}

Note that the conversion is only approximate, ideally highResNow and systemNow need to be calculated at the same time but there will be a small gap between them. The conversion will also be more unreliable the further apart the input time and the current time are.

Upvotes: 2

YSC
YSC

Reputation: 40060

This comes from the fact that std::chrono::high_resolution_clock is a type alias for std::chrono::system_clock or std::chrono::steady_clock:

Class std::chrono::high_resolution_clock represents the clock with the smallest tick period provided by the implementation. It may be an alias of std::chrono::system_clock or std::chrono::steady_clock, or a third, independent clock.

This means std::chrono::high_resolution_clock::time_point can be a type alias for std::chrono::system_clock::time_point, or it can be an other type. From your question, one can guess it does for MSVC2013, making your code valid, but is not for MSVC2017, making your code invalid.

As a conclusion, the following code might or might not be valid, in an unspecified manner (it depends on the compiler and its target architecture):

std::chrono::high_resolution_clock::time_point timePoint = /* something */;
std::chrono::system_clock::to_time_t(timePoint);

Upvotes: 4

Related Questions