'Datetime parse and format in C++

I'm using time_point the first time. I want to parse datetime from string and found a difference when returning back to string from 1 hour.

std::chrono::system_clock::time_point timePoint;
std::stringstream ss("2021-01-01 00:00:09+01");
std::chrono::from_stream(ss, "%F %T%z", timePoint);  
  // timePoint == {_MyDur={_MyRep=16094556090000000 } }
std::string timePointStr = std::format("{:%Y/%m/%d %T}", floor<std::chrono::seconds>(timePoint));
  // timePointStr = "2020/12/31 23:00:09"

I don't know what is wrong: the timepoint and the parsing or the formatting to string? How can I get the same format as the parsed one?



Solution 1:[1]

This is the expected behavior.

Explanation:

system_clock::time_point, and more generally, all time_points based on system_clock, have the semantics of Unix Time. This is a count of time since 1970-01-01 00:00:00 UTC, excluding leap seconds.

So when "2021-01-01 00:00:09+01" is parsed into a system_clock::time_point, the "2021-01-01 00:00:09" is interpreted as local time, and the "+01" is used to transform that local time into UTC. When formatting back out, there is no corresponding transformation back to local time (though that is possible with additional syntax1). The format statement simply prints out the UTC time (an hour earlier).

If you would prefer to parse "2021-01-01 00:00:09+01" without the transformation to UTC, that can be done by parsing into a std::chrono::local_time of whatever precision you desire. For example:

std::chrono::local_seconds timePoint;
std::stringstream ss("2021-01-01 00:00:09+01");
from_stream(ss, "%F %T%z", timePoint);
...

Now when you print it back out, you will get "2021/01/01 00:00:09". However the value in the rep is now 1609459209 (3600 seconds later).


1 To format out at sys_time as a local_time with a UTC offset of 1h it is necessary to choose a time_zone with a UTC offset of 1h at least at the UTC time you are formatting. For example the IANA time zone of "Etc/GMT-1" always has an offset of 1h ... yes, the signs of the offset are reversed. Using this to transform 2020-12-31 23:00:09 UTC back to 2021-01-01 00:00:09 would look like:

std::chrono::sys_seconds timePoint;
std::stringstream ss("2021-01-01 00:00:09+01");
std::chrono::from_stream(ss, "%F %T%z", timePoint);
// timePoint == 2020-12-31 23:00:09 UTC
std::string timePointStr = std::format("{:%Y/%m/%d %T}",
                               std::chrono::zoned_time{"Etc/GMT-1", timePoint});
cout << timePointStr << '\n';  // 2021/01/01 00:00:09

Disclaimer: I do not currently have a way to verify that MSVC supports the "Etc/GMT-1" std::chrono::time_zone.

Fwiw, using "Africa/Algiers" or "Europe/Amsterdam" in place of "Etc/GMT-1" should give the same result for this specific time stamp. And if your computer has its local time zone set to something that has a 1h UTC offset for this timestamp, then std::chrono::current_zone() in place of of "Etc/GMT-1" will also give the same result.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1