c++parsingc++-chrono

Workaround for missing std::chrono::from_stream


C++20 introduces std::chrono::from_stream() to parse date/time from a string and store it in a std::chrono::time_point. For example:

std::stringstream ss("2018-12-09T00:00:00+0130");

std::chrono::time_point<std::chrono::utc_clock> tp;

if (std::chrono::from_stream(ss, "%FT%T%z", tp))
{
    std::cout << "Time point: " << tp << '\n';
}
else
    std::cout << "Not a valid timestamp\n";

However, GCC prior to version 14, as well as clang up to at least version 20.1, have not implemented this. Attempts to use from_stream result in a compiler error:

error: ‘from_stream’ is not a member of ‘std::chrono’

What workarounds are available in these versions? I can easily parse year/month/day/hour/minute/second/UTC offset (hours/minutes) into individual variables or a std::tm, but there doesn’t seem to be a straightforward way to construct a time_point from that.

system_clock has from_time_t() (utc_clock doesn’t), but that requires me to use legacy C code, which has its own flaws such as not being thread-safe.

How can I construct a std::chrono::time_point from pre-parsed field values, in a way that’s portable and doesn’t require new dependencies?


Solution

  • If you already have a struct tm with requested fields, you can convert it to a time_point of system_clock as follows:

    constexpr std::chrono::sys_seconds from_tm(const std::tm& t)
    {
        using namespace std::chrono;
    
        const auto y = static_cast<year>(t.tm_year + 1900);
        const auto m = static_cast<month>(t.tm_mon + 1);
        const auto d = static_cast<day>(t.tm_mday);
        const hours h{t.tm_hour};
        const minutes m{t.tm_min};
        const seconds s{t.tm_sec};
    
        sys_seconds res = sys_days{y/m/d};
        res = res + h + m + s;
    
        return res;
    }
    

    You can use std::chrono::clock_cast to convert the result to a time point of a different, compatible clock. For example:

    std::chrono::time_point<std::chrono::utc_clock> tp
      = std::chrono::clock_cast<std::chrono::utc_clock>(res);