2

Sample string:

2018-10-31T14:45:21.778952-07:00

I would like to convert it to int64_t representing milliseconds (or even microseconds) since epoch. The timezone can vary. The code will be executing on a linux box, but I only have access to std and folly (cannot use any arbitrary 3P libraries).

I searched for this and found a few different ways that do not work for me:

  1. strptime() and std::get_time() lose the millisecond precision
  2. More importantly, neither of those can deal with timezone offsets
  3. Some other solutions depend on 3P libraries

Is there some easy way to do this?

rubenvb
  • 74,642
  • 33
  • 187
  • 332
Pratik
  • 378
  • 3
  • 14
  • I found following link. I hope it will be helpful. https://stackoverflow.com/questions/4137748/c-converting-a-time-string-to-seconds-from-the-epoch – MJK Nov 13 '18 at 21:27
  • @MJK Sorry, should've mentioned I looked at that as well. Since std::get_time uses the tm struct, it loses the millisecond precision as well. – Pratik Nov 13 '18 at 22:01
  • Once you break apart each of the elements you might want to look into how OS’s convert the RTC into a unix epoch. The algorithms are pretty well known, and can usually be copied and pasted. – vandench Nov 13 '18 at 22:06
  • An example can be found here, the important part is converting a date to the number of days since 1970, I don’t remember where I originally found this information: https://forum.osdev.org/viewtopic.php?t=13635&p=93464 – vandench Nov 13 '18 at 22:25
  • I've updated my question to mention timezones as well. I can't find any solution that can correctly deal with timezone offsets outside of a 3P library like: https://howardhinnant.github.io/date/tz.html – Pratik Nov 13 '18 at 23:27
  • @van aaaaand you forgot about leap seconds. – rubenvb Nov 13 '18 at 23:28
  • @rubenvb in case you’ve forgotten the Unix epoch explicitly does not handle leap seconds. – vandench Nov 13 '18 at 23:33
  • There is no system-lvl API that allows you to deal with any timezone. Typically any given system only cares about it's own timezone and GMT. Historical timezone info (when given location switches between summer/winter time/etc) is in constant flux and keeping every server up-to-date with every government's whims is impractical. Howard's library is is best thing (I know of) that tackles this. He is using 3rd-party tz info and your app is expected to be able to periodically download tz updates (and hope that website is still up and running). – C.M. Nov 14 '18 at 00:27
  • @C.M. Timezone offsets are not nearly as difficult as timezone names. It's sad to see there is nothing in the standard library that can deal with it. I am looking into using Howard's library. However, that it makes a web call gives me pause. I assume that if the call fails it will just use the locally stored timezone name data? We won't be dealing with timezone names, so I don't care about those. However, making a network call might be an issue. – Pratik Nov 14 '18 at 00:51
  • @Pratik If standard starts demanding arbitrary tz support this will make probably all of systems currently being used non-conforming. Who needs standard like that? ;) I don't know every aspect of this, but apparently lack of trusted and reliable source of tz info is one of barriers. I am pretty sure you can provide pre-downloaded tz info to Howard's lib and ensure it never "calls" for updates, but then your logic isn't guaranteed to work with timestamps from recent past. Tbh, even having up-to-date tz info isn't a 100% guarantee because gov can (theoretically) change tz info retroactively... – C.M. Nov 14 '18 at 01:05
  • @van It’s a bit more complicaties than that: https://stackoverflow.com/questions/16539436/unix-time-and-leap-seconds#comment41898480_16539483. Essentially, wegen converting from a (UTC) datetime you’ll need to know when the clock skipped a second in Unix time. Time is a complicated animal, because we use bad metrics. – rubenvb Nov 14 '18 at 06:32
  • 1
    This particular problem doesn't require awareness of timezones since the UTC offset is part of the format. In case you change your mind on the use of [Howard Hinnant's library](https://github.com/HowardHinnant/date/blob/master/include/date/date.h), this problem requires only including a single header, and is only a few lines of code. This library has also already been voted into the C++20 working draft. And for the sample input, the result is 1541022321778952µs. – Howard Hinnant Nov 14 '18 at 14:04

1 Answers1

4

From the comments above:

I am looking into using Howard's library. However, that it makes a web call gives me pause. I assume that if the call fails it will just use the locally stored timezone name data? We won't be dealing with timezone names, so I don't care about those. However, making a network call might be an issue.

Howard's library is layered:

  1. A foundational layer that does not need the IANA time zone database and thus never makes networking calls. This is a single header, header-only library.

  2. A time zone layer that is aware of the IANA time zone database. This layer can be configured to make network calls or not (depending on build flags), or even use your OS's time zone database (except on Windows).

Your application does not require the time zone layer as it only deals with UTC offsets, and not time zone names or rules.

Here is a function that converts a std::string with your sample format into a std::chrono::time_point<std::chrono::system_clock, std::chrono::microseconds>. That type is a big verbose mouthful for a system_clock::time_point except guaranteed to have microseconds precision. The foundational layer has a type alias for this type called date::sys_time<std::chrono::microseconds>.

#include "date/date.h"
#include <chrono>
#include <iostream>
#include <sstream>

auto
to_sys_time(std::string s)
{
    using namespace date;
    using namespace std;
    using namespace std::chrono;
    istringstream in{move(s)};
    in.exceptions(ios::failbit);
    sys_time<microseconds> tp;
    in >> parse("%FT%T%z", tp);
    return tp;
}
  • Put the string into a istringstream.
  • Optionally set the istringstream to throw an exception if it fails to parse something (you may choose to handle errors differently).
  • Declare your time_point with the desired precision.
  • Parse it with your desired format.
  • Return it.

You can exercise this function like so:

int
main()
{
    auto tp = to_sys_time("2018-10-31T14:45:21.778952-07:00");
    using date::operator<<;
    std::cout << tp << " UTC\n";
    std::cout << tp.time_since_epoch() << '\n';
}
  • Call to_sys_time with the desired input string.
  • Make the streaming operators in namespace date available.
  • Print out the time_point (this is a UTC time_point).
  • Extract and print out the duration of the time_point.

The output of this program is:

2018-10-31 21:45:21.778952 UTC
1541022321778952µs

This program will port to C++20 by removing #include "date/date.h", using namespace date; and using date::operator<<;.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • 1
    Thanks, Howard! The right answer is that C++14 standard library does not have a way to parse the timezone offset, and so my only option is to either write that logic myself or use a 3P library. Your library is the best option. – Pratik Nov 16 '18 at 19:09