4

I have a date time string:

20:48:01.469 UTC MAR 31 2016

I would like to convert this string representation of time to a struct tm using strptime, but my format string isn't working.

Is there a format specifier for fractional seconds? Perhaps %S, %s, or something else?

Code snippet is below:

tm tmbuf;
const char *str = "20:48:01.469 UTC MAR 31 2016"
const char *fmt = "%H:%M:%s %Z %b %d %Y";
strptime(str,fmt,&tmbuf);
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
Dr. Debasish Jana
  • 6,980
  • 4
  • 30
  • 69
  • 1
    try `"%H:%M:%s.%f %Z %b %d %Y"` – EdChum Jun 16 '16 at 10:44
  • 1
    One small correction: `"%H:%M:%S.%f %Z %b %d %Y"` – GMichael Jun 16 '16 at 10:48
  • @Michael But "%H:%M:%S.%f %Z %b %d %Y" also don't extract properly, it does show the minute and second (integer part) but nothing else – Dr. Debasish Jana Jun 16 '16 at 10:51
  • 2
    "%H:%M:%S.%Y %Z %b %d %Y" [works for me](http://ideone.com/QkEA99). This parses the unwanted milliseconds into the year, which is then overwritten by the real year. The behaviour might be dependent on the runtime (e.g. %f seems to be not universally supported). – Karsten Koop Jun 16 '16 at 10:55
  • @Dr. Debasish Jana As far as I know, there is no built-in reading for subseconds. At least, I've never seen such a parsing. You have to parse the entire string manually if you need that accuracy. If you don't need it, use Karsten Koop's recommendation – GMichael Jun 16 '16 at 10:57
  • @KarstenKoop but "%H:%M:%S.%Y %Z %b %d %Y" couldn't extract either – Dr. Debasish Jana Jun 16 '16 at 11:02
  • Wait, do you want to extract the milliseconds? There's no field for them in `struct tm`. – Karsten Koop Jun 16 '16 at 11:03
  • @KarstenKoop without the millisecs and timezone, the extraction works, but the data comes with these, and I would like to ignore these parts if needed – Dr. Debasish Jana Jun 16 '16 at 11:39
  • @KarstenKoop Your solution was the one I went with as well. Since you got it in there first I've linked your comment in [my answer](http://stackoverflow.com/a/37857557/2642059) but since that didn't seem to work for the OP I've also provided a couple alternate solutions. – Jonathan Mee Jun 16 '16 at 13:31

2 Answers2

3

Using this free, open source C++11/14 library, here is another way to deal with parsing fractional seconds:

#include "tz.h"
#include <iostream>
#include <sstream>

int main()
{
    using namespace date;
    using namespace std::chrono;
    std::istringstream str("20:48:01.469 UTC MAR 31 2016");
    sys_time<milliseconds> tp;
    parse(str, "%T %Z %b %d %Y", tp);
    std::cout << tp << '\n';
}

Output:

2016-03-31 20:48:01.469

I.e., with this tool %S and %T just work. The precision is controlled not with flags, but with the precision of the std::chrono::time_point.

If you want to find out what timezone abbreviation you parsed, that is also possible:

std::istringstream str("20:48:01.469 UTC MAR 31 2016");
sys_time<milliseconds> tp;
std::string abbrev;
parse(str, "%T %Z %b %d %Y", tp, abbrev);
std::cout << tp << ' ' << abbrev << '\n';

Output:

2016-03-31 20:48:01.469 UTC

This being said, this library is built on top of std::get_time and thus has the same portability problem that Jonathan's excellent (and upvoted) answer alludes to: Only libc++ currently parses month names in a case-insensitive manner. Hopefully that will change in the not-too-distant future.

libstdc++ bug report.

VSO#232129 bug report.

If you have to deal with timezones other than UTC, in general, there is no sure-fire method to do that, because at any one time, more than one timezone can be using the same abbreviation. So the UTC offset can be ambiguous. However here is a short article on how to use this library to narrow down an abbreviation to a list of candidate timezones from which you might have some ad hoc logic for choosing a unique timezone.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • Have to say I expected you to have dropped an answer in here before I'd finished all my edits. When is your library getting into the standard? – Jonathan Mee Jun 16 '16 at 18:15
  • 1
    @JonathanMee: You caught me napping. ;-) Standardization is a glacial process fraught with failure. I've submitted http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0355r0.html only a few weeks ago. It will be considered next week in Oulu. I have no idea what the outcome will be. But I can assure you there will not be unanimous agreement. `` itself was very contentious and difficult to standardize for C++11. – Howard Hinnant Jun 16 '16 at 19:21
  • 1
    Oh snap! Best of luck to you sir. I find myself gravitating to these time issues somehow because I've put a lot of time into them I guess, but I keep thinking man, if I just had @HowardHinnant's library I could answer this easier. – Jonathan Mee Jun 16 '16 at 19:27
  • 2
    @JonathanMee: Please feel free to use my lib to answer these questions! :-) Ported to Mac, Linux and Windows. Windows will soon be upgraded to do the auto-download dance. – Howard Hinnant Jun 16 '16 at 19:39
  • You know it really excites me to learn and explain stuff that is forthcoming in the standard. The standard really needs this library, so I'm sure you'll at least come away from Oulu with an issue number. Lemme know when you get it so I can start linking to it! – Jonathan Mee Jun 16 '16 at 20:05
1

Note that tm's member denoting the smallest time increment is tm_sec, which is an int which is defined only over the range:

Seconds after the minute [0,60] since C++11

So you won't be able to store a fraction of a second in a tm, you'll just need to discard the number following the decimal place.

As suggested by Karsten Koop you can just read the year twice, the second %Y will stomp the first:

auto fmt = "%H:%M:%S.%Y %Z %b %d %Y";

Live Example


That said, I'd recommend against using strptime it is a POSIX function, using a standard function like get_time would be preferable. This has one minor drawback; get_time doesn't have knowledge of time zones, but then neither does tm, with the exception of tm_isdst which is the:

Daylight Saving Time flag. The value is positive if DST is in effect, zero if not and negative if no information is available

So you may have to assign tm_isdst independently if you persue something like this:

tm tmbuf;
stringstream str("20:48:01.469 UTC MAR 31 2016");

str >> get_time(&tmbuf, "%H:%M:%S.%Y UTC %b %d %Y");

Live Example


My get_time answer was a little hypocritical, cause while I speak of the importance of standardization I could only get it to run on libc++. As such I thought I'd post a more universal solution, which will also discard the time zone, so again you'll need to set tm_isdst independently:

tm tmbuf{};
stringstream str("20:48:01.469 UTC MAR 31 2016");
string tm_mon;

str >> get_time(&tmbuf, "%T");

str.ignore(std::numeric_limits<std::streamsize>::max(), 'C');

str >> tm_mon >> get_time(&tmbuf, "%d %Y");

for (const auto& i : { "JAN"s, "FEB"s, "MAR"s, "APR"s, "MAY"s, "JUN"s, "JUL"s, "AUG"s, "SEP"s, "OCT"s, "NOV"s, "DEC"s }) {
    if (equal(cbegin(tm_mon), cend(tm_mon), cbegin(i), cend(i), [](const unsigned char a, const unsigned char b) { return toupper(a) == b; })) break;
    ++tmbuf.tm_mon;
}

Live Example

This has 2 key dependencies:

  1. That the timezone always ends in the character 'C' (it must be uppercase)
  2. That the month abbreviations fed in match one of those in my initializer_list
Community
  • 1
  • 1
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288