Let's assume we have a simple function that takes a std::chrono::time_point and returns a string using a given formatting string. Like so:
std::string DateTimeToString(std::chrono::sys_time, const char * szFormat /*= "%Y/%m/%d %H:%M:%S"*/)
According to the cppreference documentation of std::formatter we should be able to use the individual tags to create a string like 2021/03/24 15:36:32.123123
. However, when the duration of the time_point uses microseconds (which is mandatory for our use) then this throws a format_error.
If we split this into to separate calls so that we use format for the date and time separately then it works. Like this:
auto tSysTime = std::chrono::system_clock::now();
std::string sTime = std::format("{:%H:%M:%S}", tSysTime.time_since_epoch());
std::string sDate = std::format("{:%Y/%m/%d}", std::chrono::sys_days(std::chrono::duration_cast<std::chrono::days>(tSysTime.time_since_epoch())));
Am I missunderstanding something or is there no way to have a single format string for my purpose? Keep in mind that the format string is an optional parameter so I cannot hardcode a split of individual parameters.
My expection would be to write
std::format("{:%Y/%m/%d %H:%M:%S}",tSysTime.time_since_epoch());
However, this crashes...
Thank you in advance for any help!
Edit: I'm using /std::c++latest flag in Visual Studio 2019
You need:
std::format("{:%Y/%m/%d %H:%M:%S}",tSysTime);
Or you can simplify it to:
std::format("{:%Y/%m/%d %T}",tSysTime);
If your system_clock::time_point::duration
isn't microseconds
, you can force it to microseconds precision with:
std::format("{:%Y/%m/%d %T}",floor<microseconds>(tSysTime));
The reason std::format("{:%Y/%m/%d %H:%M:%S}",tSysTime.time_since_epoch());
fails is that the .time_since_epoch()
turns the time_point
into a duration
. And duration
s know nothing about dates. For example if you asked me what the current date and time is and I told you 1,616,600,192,123,123µs, you would look at me funny. A duration
knows nothing about an epoch. A duration
is just a measure of time between two time_point
s.
A time_point
on the other hand, knows about an epoch. It holds a duration
and means: This much time duration
beyond (or before) my epoch.
std::format
understands the distinction between time_point
and duration
. And so if the format string asks for dates (e.g. %Y/%m/%d
) and you are formatting a duration
, it throws an exception indicating insufficient information to do the requested job. But if you give it a time_point
, then std::format
knows how to get the date information out of that.
This is all part of the type-safety design that <chrono>
is built on, so that one can catch as many logic errors as possible, as early as possible.
int64_t i = 1'616'600'192'123'123;
microseconds d{i};
sys_time<microseconds> t{d};
cout << "i = " << i << '\n';
cout << "d = " << d << '\n';
cout << "t = " << t << '\n';
i
, d
and t
all hold the same value in memory: 1,616,600,192,123,123, but mean different things. And you can see these different meanings when you print them out:
i = 1616600192123123
d = 1616600192123123µs
t = 2021-03-24 15:36:32.123123
This is no different than:
char x = 'A';
int y{x};
cout << "x = " << x << '\n'; // x = A
cout << "y = " << y << '\n'; // y = 65