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.
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.