Search code examples
c++clinuxdatetimeepoch

Converting date, time and UTC offset values to milliseconds since unix epoch?


I am working on a robotics project which has sensor data from different locations with millisecond precision (gps synchronised ntp to within 1ms).

How to convert historical date, time and UTC offset components into milliseconds since unix epoch? That is, all YYYY-mm-dd HH:MM:SS.mS +/-UTC offset are available as individual integers and not a string.

The offset accounts for daylight savings so for example a timezone of UTC +1 will have a UTC offset of +2 during daylight savings.

There is a similar post however it dealt with a datetime in a string format with no UTC offset information.

The accepted answer from that post was to use mktime however in this post a 60K+ rep user commented that:

mktime will use your computer's locale to convert that date/time into GMT. If you do not want to have your local timezone subtracted, then use mkgmtime.

I've confirmed that mktime does perform timezone conversions which are dependent on the daylight savings flag tm_isdst. I don't have information whether or not the observations were taken during daylight savings or not, only their offset to UTC.

What is a robust way to convert historical datetimes with UTC offsets into milliseconds since unix epoch?


Solution

  • Here is a free, open-source C++11/14 header-only library which will do the job. It is fully documented, and portable across all C++11/14 implementations. There is even a video presentation of it.

    #include "chrono_io.h"
    #include "date.h"
    #include <iostream>
    #include <sstream>
    
    int
    main()
    {
        using namespace std;
        using namespace std::chrono;
        using namespace date;
        istringstream in{"2016-10-10 23:48:59.456 -4"};
        sys_time<milliseconds> tp;
        int ih;
        in >> parse("%F %T", tp) >> ih;
        tp -= hours{ih};
        cout << tp.time_since_epoch() << '\n';
    }
    

    This outputs:

    1476157739456ms
    

    Which is the correct number of Unix Time milliseconds since the epoch. If you need to include leap seconds, there are facilities for that at the above links, but that part is not header-only (requires using the IANA timezone database).

    This library is <chrono>-based. In particular the type of tp is std::chrono::time_point<std::chrono::system_clock, std::chrono::milliseconds>, but with prettier syntax.

    If your input stream followed the standard UTC offset syntax of +/-hhmm instead of +/-[h]h, the code could be simplified to:

    istringstream in{"2016-10-10 23:48:59.456 -0400"};
    sys_time<milliseconds> tp;
    in >> parse("%F %T %z", tp);
    

    which would output the exact same value for tp.

    1476157739456ms
    

    This modified syntax is also supported:

    istringstream in{"2016-10-10 23:48:59.456 -4:00"};
    sys_time<milliseconds> tp;
    in >> parse("%F %T %Ez", tp);
    

    If you want the integral value of milliseconds, that is available with <chrono>'s duration.count() member function:

    cout << tp.time_since_epoch().count() << '\n';
    
    1476157739456
    

    Any way you slice it, this is a few lines of type-safe code with no explicit conversion factors.

    I noticed you tagged your question with both [c++] and [c]. This answer addresses only the C++ language. C and C++ are two distinct languages, and if you must program in C, one of the other answers which program down at the C level of abstraction would be better.

    For the curious, with any of these variants, if you just output tp:

    cout << tp << '\n';
    

    Then it outputs the expected UTC timestamp:

    2016-10-11 03:48:59.456