Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how do I parse an iso 8601 date (with optional milliseconds) to a struct tm in C++?

I have a string which should specify a date and time in ISO 8601 format, which may or may not have milliseconds in it, and I am wanting to get a struct tm from it as well as any millisecond value that may have been specified (which can be assumed to be zero if not present in the string).

What would be involved in detecting whether the string is in the correct format, as well as converting a user-specified string into the struct tm and millisecond values?

If it weren't for the millisconds issue, I could probably just use the C function strptime(), but I do not know what the defined behavior of that function is supposed to be when the seconds contain a decimal point.

As one final caveat, if it is at all possible, I would greatly prefer a solution that does not have any dependency on functions that are only found in Boost (but I'm happy to accept C++11 as a prerequisite).

The input is going to look something like:

2014-11-12T19:12:14.505Z 

or

2014-11-12T12:12:14.505-5:00 

Z, in this case, indicates UTC, but any time zone might be used, and will be expressed as a + or - hours/minutes offset from GMT. The decimal portion of the seconds field is optional, but the fact that it may be there at all is why I cannot simply use strptime() or std::get_time(), which do not describe any particular defined behavior if such a character is found in the seconds portion of the string.

like image 438
markt1964 Avatar asked Nov 12 '14 19:11

markt1964


People also ask

How do I read the ISO 8601 date format?

ISO 8601 represents date and time by starting with the year, followed by the month, the day, the hour, the minutes, seconds and milliseconds. For example, 2020-07-10 15:00:00.000, represents the 10th of July 2020 at 3 p.m. (in local time as there is no time zone offset specified—more on that below).

What is Z in ISO 8601 date format?

Z is the zone designator for the zero UTC offset. "09:30 UTC" is therefore represented as "09:30Z" or "T0930Z". "14:45:15 UTC" would be "14:45:15Z" or "T144515Z". The Z suffix in the ISO 8601 time representation is sometimes referred to as "Zulu time" because the same letter is used to designate the Zulu time zone.

Is 8601 date format SAS?

The SAS ISO 8601 formats for UTC with a time zone offset are based on the following time, datetime, and time zone offsets: the zero meridian time or datetime near Greenwich, England (The offset is always +|–0000 or +|–00:00.)


1 Answers

New answer for old question. Rationale: updated tools.

Using this free, open source library, one can parse into a std::chrono::time_point<system_clock, milliseconds>, which has the advantage over a tm of being able to hold millisecond precision. And if you really need to, you can continue on to the C API via system_clock::to_time_t (losing the milliseconds along the way).

#include "date.h" #include <iostream> #include <sstream>  date::sys_time<std::chrono::milliseconds> parse8601(std::istream&& is) {     std::string save;     is >> save;     std::istringstream in{save};     date::sys_time<std::chrono::milliseconds> tp;     in >> date::parse("%FT%TZ", tp);     if (in.fail())     {         in.clear();         in.exceptions(std::ios::failbit);         in.str(save);         in >> date::parse("%FT%T%Ez", tp);     }     return tp; }  int main() {     using namespace date;     using namespace std;     cout << parse8601(istringstream{"2014-11-12T19:12:14.505Z"}) << '\n';     cout << parse8601(istringstream{"2014-11-12T12:12:14.505-5:00"}) << '\n'; } 

This outputs:

2014-11-12 19:12:14.505 2014-11-12 17:12:14.505 

Note that both outputs are UTC. The parse converted the local time to UTC using the -5:00 offset. If you actually want local time, there is also a way to parse into a type called date::local_time<milliseconds> which would then parse but ignore the offset. One can even parse the offset into a chrono::minutes if desired (using a parse overload taking minutes&).

The precision of the parse is controlled by the precision of the chrono::time_point you pass in, instead of by flags in the format string. And the offset can either be of the style +/-hhmm with %z, or +/-[h]h:mm with %Ez.

like image 177
Howard Hinnant Avatar answered Sep 22 '22 21:09

Howard Hinnant