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.
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).
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.
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.)
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With