简体   繁体   中英

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?

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

If you have the timezone in terms of hours +/- UTC, you can first use mktime (as the linked answer describes) to convert the date/time to seconds since the epoch. Then, add/subtract the timezone difference.

EDIT:

Since the mktime function assumes the given time is for the current timezone, you'll need to shift the result by that amount as well.

First, run the tzset() function. This sets two global variables: timezone which is the number of seconds the current time is behind GMT, and daylight which indicates whether daylight savings is in effect.

struct tm my_time;
// populate fields
int my_tz;
// set to timezone in question as seconds ahead of UTC
time_t t = mktime(&my_time);
t += timezone;
if (daylight) {
    t -= 3600;
}
t -= my_tz;

If you look to the accepted answer of the question you referred to, it deals with individual integers. It splits string to parts, converts them to integers and then works with them. Just drop the conversion part.

tm t;
// Fill tm_sec, tm_min, tm_hour and so on
return (mktime(&t) - tz * SECONDS_IN_TIMEZONE) * 1000 + milliseconds

If timezone is an integer, you can multiply it by number of seconds in timezone and substract if from the time. +4 zone means that time is 4 hours greater then UTC. Also note that not all time zones have integral number of hours. So you may want to store timezone in seconds/milliseconds or at least minutes.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM