markt1964 markt1964 - 4 months ago 58
C++ Question

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.

Answer

You can use C's sscanf (http://www.cplusplus.com/reference/cstdio/sscanf/) to parse it:

const char *dateStr = "2014-11-12T19:12:14.505Z";
int y,M,d,h,m;
float s;
sscanf(dateStr, "%d-%d-%dT%d:%d:%fZ", &y, &M, &d, &h, &m, &s);

If you have std::string it can be called like this (http://www.cplusplus.com/reference/string/string/c_str/):

std::string dateStr = "2014-11-12T19:12:14.505Z";
sscanf(dateStr.c_str(), "%d-%d-%dT%d:%d:%fZ", &y, &M, &d, &h, &m, &s);

If it should handle different timezones you need to use sscanf return value - number of parsed arguments:

int tzh = 0, tzm = 0;
if (6 < sscanf(dateStr.c_str(), "%d-%d-%dT%d:%d:%f%d:%dZ", &y, &M, &d, &h, &m, &s, &tzh, &tzm)) {
    if (tzh < 0) {
       tzm = -tzm;    // Fix the sign on minutes.
    }
}

And then you can fill tm (http://www.cplusplus.com/reference/ctime/tm/) struct:

tm time;
time.tm_year = y - 1900; // Year since 1900
time.tm_mon = M - 1;     // 0-11
time.tm_mday = d;        // 1-31
time.tm_hour = h;        // 0-23
time.tm_min = m;         // 0-59
time.tm_sec = (int)s;    // 0-61 (0-60 in C++11)

It also can be done with std::get_time (http://en.cppreference.com/w/cpp/io/manip/get_time) since C++11 as @Barry mentioned in comment how do I parse an iso 8601 date (with optional milliseconds) to a struct tm in C++?