51

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

Ziezi
  • 6,375
  • 3
  • 39
  • 49
Seihyung Oh
  • 683
  • 1
  • 6
  • 13

5 Answers5

37

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
Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • 22
    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. – einpoklum Oct 23 '19 at 12:45
  • 5
    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`. – Howard Hinnant Oct 23 '19 at 19:13
  • @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`? – C.M. Nov 02 '20 at 22:53
  • 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). – Howard Hinnant Nov 02 '20 at 23:06
23

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.

robinCTS
  • 5,746
  • 14
  • 30
  • 37
srdjan.veljkovic
  • 2,468
  • 16
  • 24
  • Why is this preferable to using Mr. Hinnant's library? – einpoklum Oct 23 '19 at 12:46
  • 3
    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). – srdjan.veljkovic Oct 31 '19 at 11:08
  • localtime is not thread safe! – Fabio Dec 15 '21 at 04:23
  • Correct me if I'm wrong, but is this not precise only to the nearest second? – Edward Falk Mar 31 '22 at 04:34
  • 1
    @EdwardFalk You are right, `struct tm` only has seconds, it does not include any smaller time unit measure. – srdjan.veljkovic Apr 17 '22 at 19:25
23

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
Adrian Maire
  • 14,354
  • 9
  • 45
  • 85
  • 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. – kyb Nov 06 '19 at 14:58
  • 1
    Correct, if you have access to C++17, you can optimize this code with `std::string_view`, otherwise, `const char*` is also a possibility. – Adrian Maire Nov 06 '19 at 15:38
  • I replaced time_point with auto – alboforlizo May 22 '23 at 14:52
1

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!

Maxim Paperno
  • 4,485
  • 2
  • 18
  • 22
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.

Reed Hedges
  • 1,590
  • 2
  • 15
  • 17