How to convert std::chrono::time_point to string
Asked Answered
H

6

59

How to convert std::chrono::time_point to string? For example: "201601161125".

Hardandfast answered 18/1, 2016 at 14:28 Comment(3)
Have you tried inserting it in std::stringstream and then converting it to string using stringstream's member function .str()?Chaoan
No, I didn't. I'll do that. If your tip works, I'll let you know.Hardandfast
See: howardhinnant.github.io/date_v2.html Section "What about a date-time type?"Fivestar
F
40

Update for C++20:

This can now easily be done in C++20:

#include <chrono>
#include <format>
#include <iostream>
#include <string>

int
main()
{
    using namespace std::chrono_literals;
    std::chrono::time_point tp = std::chrono::sys_days{2016y/1/16} + 11h + 25min;
    std::string s = std::format("{:%Y%m%d%H%M}", tp);
    std::cout << s << '\n';
}

Output:

201601161125

Demo.

Original Answer:

Howard Hinnant's free, open source, header-only, portable date/time library is a modern way to do this that doesn't traffic through the old C API, and doesn't require that you discard all of your sub-second information. This library is also being proposed for standardization.

There is a lot of flexibility in formatting. The easiest way is to just stream out:

#include "date.h"
#include <iostream>

int
main()
{
    using namespace date;
    std::cout << std::chrono::system_clock::now() << '\n';
}

This just output for me:

2017-09-15 13:11:34.356648

The using namespace date; is required in order to find the streaming operator for the system_clock::time_point (it isn't legal for my lib to insert it into namespace std::chrono). No information is lost in this format: the full precision of your system_clock::time_point will be output (microseconds where I ran this on macOS).

The full suite of strftime-like formatting flags is available for other formats, with minor extensions to handle things like fractional seconds. Here is another example that outputs with millisecond precision:

#include "date.h"
#include <iostream>

int
main()
{
    using namespace date;
    using namespace std::chrono;
    std::cout << format("%D %T %Z\n", floor<milliseconds>(system_clock::now()));
}

which just output for me:

09/15/17 13:17:40.466 UTC
Fivestar answered 15/9, 2017 at 13:18 Comment(4)
I think it's better to have the examples here not using namespace whatever, so that it's clearer which functions and types come from which namespace.Premier
One of the problems is that the namespaces are changing with time: floor was in namespace date, but starting with C++17 is in namespace std::chrono. By C++20, everything will be in std::chrono.Fivestar
@HowardHinnant Is there an easy way to discard trailing zeroes (and ., if necessary) in sub-second part of string produced by date::format("%FT%TZ", p) where p is a system_clock::time_point?Snowbound
The best way to control the precision of the output is to truncate the precision of the input to format, such as the truncation to milliseconds in the example above. There is no automatic way to detect trailing zeroes at run-time. the only way to do so is to do custom post-processing on the generated string (reverse searching for zeroes).Fivestar
W
24

The most flexible way to do so is to convert it to struct tm and then use strftime (it's like sprintf for time). Something like:

std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
std::time_t now_c = std::chrono::system_clock::to_time_t(now);
std::tm now_tm = *std::localtime(&now_c);
/// now you can format the string as you like with `strftime`

Look up the documentation for strftime here.

If you have localtime_s or localtime_r available you should use either in preference to localtime.

There are many other ways to do this, but, while mostly easier to use, these result in some predefined string representation. You could just "hide" all of the above in a function for ease of use.

Worley answered 18/1, 2016 at 15:46 Comment(5)
Why is this preferable to using Mr. Hinnant's library?Premier
Mr. Hinnant's library is not part of the C++ standard yet, while strftime() has been a part of C standard for a long time. It seems that in C++20 Mr. Hinnant's library will be a part of the C++ standard - when that happens, it will be hard to make a firm preference (it would depend on your needs and experience).Worley
localtime is not thread safe!Bathhouse
Correct me if I'm wrong, but is this not precise only to the nearest second?Minim
@EdwardFalk You are right, struct tm only has seconds, it does not include any smaller time unit measure.Worley
A
24

Code solution

The following function converts from chrono time_point to string (serialization).

#include <chrono>
#include <iomanip>
#include <sstream>

using time_point = std::chrono::system_clock::time_point;
std::string serializeTimePoint( const time_point& time, const std::string& format)
{
    std::time_t tt = std::chrono::system_clock::to_time_t(time);
    std::tm tm = *std::gmtime(&tt); //GMT (UTC)
    //std::tm tm = *std::localtime(&tt); //Locale time-zone, usually UTC by default.
    std::stringstream ss;
    ss << std::put_time( &tm, format.c_str() );
    return ss.str();
}

// example
int main()
{
    time_point input = std::chrono::system_clock::now();
    std::cout << serializeTimePoint(input, "UTC: %Y-%m-%d %H:%M:%S") << std::endl;

}

Time zone

The time_point data-type has no internal representation for the time-zone, in consequence, the time-zone is aggregated by the conversion to std::tm (by the functions gmtime or localtime). It is not recommended to add/substract the time-zone from the input, because you would get an incorrect time-zone displayed with %Z, thus, it is better to set the correct local time (OS dependent) and use localtime().

Technical vs User-friendly serialization

For technical usage, hard-coded time format is a good solution. However, to display to users, one should use a locale to retrieve the user preference and show time-stamp in that format.

C++20

Since C++20, we have nice serialization and parsing functions for time_point and duration.

  • std::chrono::to_stream
  • std::chrono::format
Absently answered 23/10, 2019 at 12:42 Comment(3)
You create std::string from literal and then take c_str() from it => redundant malloc under hood. There is std::string_view for such purposes.Citronellal
Correct, if you have access to C++17, you can optimize this code with std::string_view, otherwise, const char* is also a possibility.Absently
I replaced time_point with autoIlllooking
O
2

A lot like Adrian's answer, but with (optional) milliseconds and GMT/localtime switch.

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

using Clock = std::chrono::high_resolution_clock;
static std::string timePointToString(const Clock::time_point &tp, const std::string &format, bool withMs = true, bool utc = true)
{
    const Clock::time_point::duration tt = tp.time_since_epoch();
    const time_t durS = std::chrono::duration_cast<std::chrono::seconds>(tt).count();
    std::ostringstream ss;
    if (const std::tm *tm = (utc ? std::gmtime(&durS) : std::localtime(&durS))) {
        ss << std::put_time(tm, format.c_str());
        if (withMs) {
            const long long durMs = std::chrono::duration_cast<std::chrono::milliseconds>(tt).count();
            ss << std::setw(3) << std::setfill('0') << int(durMs - durS * 1000);
        }
    }
    // gmtime/localtime() returned null ?
    else {
        ss << "<FORMAT ERROR>";
    }
    return ss.str();
}

int main() {
    const auto tp = Clock::now();
    std::cout << timePointToString(tp, "%Z %Y-%m-%d %H:%M:%S.") << std::endl;
    return 0;
}

> GMT 2022-04-30 13:44:09.720

Test: https://www.mycompiler.io/view/43wsMbrMcmx

Granted it only appends the milliseconds and they're not part of the template... that would cost extra!

Oceanus answered 30/4, 2022 at 13:45 Comment(0)
P
0

It will become easier with C++20 (as mentioned in Adrian Maire's answer.)

#include <chrono>
#include <format>

#include <string>
#include <iostream>

int main()
{
  auto t = std::chrono::system_clock::now();
  std::string s = std::format("{0:%F %R %Z}", t);
  std::cout << s << '\n';
  return 0;
}

Note, As of now (February 2023) std::format is not implemented yet in most compiler releases, but hopefully will be soon in new releases. (You can check with preprocessor macros __has_include(<format>) and __cpp_lib_format.)

Docs on std::format: https://en.cppreference.com/w/cpp/utility/format/format

The format syntax ("%F %T %Z") is similar to strftime() or std::put_time() (haven't checked what the differences are, though) and documented at https://en.cppreference.com/w/cpp/chrono/system_clock/formatter.

Pilau answered 5/2, 2023 at 14:26 Comment(0)
D
0

std::chrono::system_clock::time_point to string and back including hundreds of nanoseconds (the natural resolution of system_clock::time_point):

I couldn't find answer for c++17, so I wrote it myself. Sorry, if I re-inventing the wheel, but it's really ugly 100 nanoseconds here. Took too much of my time, hope my answer will be helpful.

The format "%Y-%m-%d %H:%M:%S.7sybmols_of_hundreds_ns"

And, yeah, you gonna look how to split the string

std::string Utils::From(const std::chrono::system_clock::time_point& datetime) {
        auto t = std::chrono::system_clock::to_time_t(datetime);
        auto datetime_se = datetime.time_since_epoch();
        auto datetime_se_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(datetime_se);
        auto hundreds_ns = (datetime_se_ns % 1000000000)/100;  // 1000000000 - ns, std::chrono::system_clock 100 ns   
        std::tm tm = {};
        gmtime_s(&tm, &t);
        std::stringstream ss;
        ss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S");
        ss << '.' << std::setfill('0') << std::setw(7) << hundreds_ns.count();
        return ss.str();
    }
    
       std::chrono::system_clock::time_point Utils::ToTimePoint(const std::string& datetime) {
        auto tokens = Utils::SplitString(datetime, ".");
        auto time_t = tokens[0];
        auto hundreds_ns_str = tokens[1];
    
        std::tm tm = {};
        std::istringstream ss(time_t);
        ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S");
        auto t = _mkgmtime(&tm);
        std::chrono::system_clock::time_point tp = std::chrono::system_clock::from_time_t(t);
    
        std::istringstream ss_h_ns(hundreds_ns_str);
        int hundreds_ns;
        ss_h_ns >> hundreds_ns;
        tp += std::chrono::duration_cast<std::chrono::system_clock::duration>(
            std::chrono::nanoseconds(hundreds_ns*100));
        return tp ;
    }
Dromond answered 21/6 at 3:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.