Search code examples
c++c++11gpsutcdata-conversion

How to convert gps time to utc in c++?


I am collecting GPS time (in ns) from a sensor and I am looking for a way to convert that to a UTC time in C++.

I have a working code before in python.

    time_gps = time_gps * 10**(-9)    # Converts ns -> s
    gps_epoch = pd.datetime(year=1980, month=1, day=6)
    delta = pd.to_timedelta(time_gps, unit='s')
    time = gps_epoch + delta - pd.to_timedelta(19, unit='s')

Using the link "Using std::chrono / date::gps_clock for converting a double gps timestamp to utc/tai" helped me figure out how to convert from GPS time to UTC.

    uint64_t gps_input_ns = 1281798087485516800;         
    date::gps_time<std::chrono::nanoseconds> gt_nano{date::round<std::chrono::nanoseconds>(std::chrono::duration<uint64_t, std::nano>{gps_input_ns})};
    auto utc_nano = date::clock_cast<date::utc_clock>(gt_nano);
    std::cout << utc_nano << " UTC\n";
Output: 2020-08-18 15:01:09.485516800 UTC

My next question is, how can I extract the date and time from the variable "utc_nano"? I'm not very familiar with chrono or the date library and therefore having problems trying to separate the date and time. Any help would be much appreciated.


Solution

  • I'm assuming that leap seconds are important to you since you're dealing with gps time which represents the physical seconds that are labeled leap seconds by UTC. It is fairly tricky to manipulate date/times with leaps seconds, which is why Unix Time is so popular in computer systems.

    In the C++20 chrono preview library, Unix Time is modeled by sys_time, whereas true UTC is modeled by utc_time. The only difference between these two models is that sys_time doesn't count leap seconds and utc_time does.

    The advantage of sys_time is that there exists a fast and efficient algorithm for translating the time duration since 1970-01-01 00:00:00 into fields: year, month, day, hour, minute, second, subseconds. So if you want to break utc_time into these fields, the trick is to first turn utc_time into sys_time, while remembering whether or not your utc_time is referencing a leap second. Indeed, this is exactly what the streaming operator for utc_time does.

    There exists a helper function get_leap_second_info to aid in doing this. This function takes a utc_time and returns a {is leap second, count of leap seconds} struct. The first member is true if the argument refers to a leap second, the second argument tells you how many leap seconds there have been between the argument and 1970. So the first step is to get this information for utc_nano:

    auto info = get_leap_second_info(utc_nano);
    

    Now you can create a sys_time with this information. Since sys_time is just like utc_time excluding leap seconds, you can just subtract off the number of leap seconds that have occurred:

    sys_time<nanoseconds> sys_nano{utc_nano.time_since_epoch() - info.elapsed};
    

    Now you have a count of nanoseconds in Unix Time. Truncating to days precision gives you a count of days in Unix Time:

    auto sys_day = floor<days>(sys_nano);
    

    sys_day is a date. The time of day is simply the difference between the nanoseconds-precision time_point and the days-precision time_point:

    auto tod = sys_nano - sys_day;
    

    tod is a time. It is the duration since midnight. It may be short by a second. That information is in info.is_leap_second.

    If you want these types as "field types", you could convert sys_day to type year_month_day:

    year_month_day ymd = sys_days;
    

    year_month_day has getters for year, month and day.

    You can convert tod into a {hours, minutes, seconds, nanoseconds} struct with:

    hh_mm_ss hms{tod};
    

    This has getters: hours(), minutes(), seconds(), and subseconds(). The above syntax assumes C++17. If in C++11 or 14, the syntax is:

    hh_mm_ss<nanoseconds> hms{tod};
    

    hh_mm_ss doesn't directly support a count of 60s, but that information is still in info.is_leap_second. E.g.

    std::cout << hms.seconds().count() + info.is_leap_second << '\n';
    

    That will output 60 if and only if info.is_leap_second is true.