如何在C++中将GPS时间转换为UTC时间?

4

我正在从传感器收集GPS时间(以纳秒为单位),希望找到一种在C++中将其转换为UTC时间的方法。

之前我已经有了一个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')

使用链接 "使用 std::chrono / date::gps_clock 将双精度 GPS 时间戳转换为 UTC/TAI" 帮助我弄清了如何将 GPS 时间转换为 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

我的下一个问题是,如何从变量“utc_nano”中提取日期和时间?我对chrono或日期库并不很熟悉,因此在尝试分离日期和时间时遇到了问题。非常感谢任何帮助。


2
请注意,这是一个重复但有用的信息:使用std :: chrono / date :: gps_clock将双精度GPS时间戳转换为UTC / TAI。经过再次考虑,如果您选择使用chrono方法,则可能会出现重复。 - user4581301
你能更具体地说明“获取”吗?你想要的结果是终端输出还是字符串?或者你想要表示日期和时间分别的chrono类型?换句话说,这是一个格式化问题还是数据结构问题? - Howard Hinnant
@HowardHinnant 我编辑了我的问题,希望更具体地回答您的问题。我所说的“获取”是从变量“utc_nano”中提取日期和时间。 - Hello_Aiden
2个回答

6
我假设你对闰秒非常重视,因为你正在处理GPS时间,该时间代表了UTC标记为闰秒的物理秒。操作带有闰秒的日期/时间相当棘手,这就是为什么Unix Time在计算机系统中如此受欢迎的原因。
C++20 chrono preview library中,Unix Timesys_time建模,而真正的UTC由utc_time建模。这两个模型之间唯一的区别是sys_time不计算闰秒,而utc_time则计算闰秒。 sys_time的优点在于存在一种快速高效的算法,可以将自1970年01月01日00:00:00以来的时间持续时间转换为字段:年、月、日、小时、分钟、秒、亚秒。因此,如果您想将utc_time分解为这些字段,诀窍是首先将utc_time转换为sys_time,同时记住您的utc_time是否引用了闰秒。实际上,这正是utc_time的流操作符所做的事情。
存在一个辅助函数get_leap_second_info来帮助执行此操作。该函数获取utc_time并返回一个{is leap second, count of leap seconds}结构体。第一个成员为true,如果参数涉及闰秒,则第二个参数告诉您从1970年以来有多少闰秒。因此,第一步是获取utc_nano的此信息:
auto info = get_leap_second_info(utc_nano);

现在你可以使用这些信息创建一个sys_time。由于sys_timeutc_time类似,只是不包括闰秒,因此您可以减去已发生的闰秒数量:
sys_time<nanoseconds> sys_nano{utc_nano.time_since_epoch() - info.elapsed};

现在您拥有Unix时间中的纳秒计数。将其截断到精度,可以得到Unix时间中的天数计数:
auto sys_day = floor<days>(sys_nano);

sys_day 是一个日期。时间只是纳秒精度的时间点和天精度的时间点之间的差异:

auto tod = sys_nano - sys_day;

tod是一个时间。它是从午夜开始的持续时间。它可能短一秒钟。这些信息包含在info.is_leap_second中。

如果您想将这些类型作为“字段类型”,您可以将sys_day转换为year_month_day类型:

year_month_day ymd = sys_days;

year_month_day有获取yearmonthday的方法。

您可以使用以下代码将tod转换为{hours, minutes, seconds, nanoseconds}结构体:

hh_mm_ss hms{tod};

这里有四个获取器:hours()minutes()seconds()subseconds()。上述语法假定使用的是C++17。如果使用C++11或14,则语法如下:
hh_mm_ss<nanoseconds> hms{tod};

hh_mm_ss 不直接支持60秒的计数,但该信息仍在info.is_leap_second中。例如:

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

只有当info.is_leap_second为真时,才会输出60。

谢谢您的帮助!我的方法不同,但步骤类似。
  • 我使用clock_cast获取了sys_time: auto utc_sys = clock_cast<system_clock>(utc_nano)
  • 然后我使用floor函数获取了sys_days: auto utc_dp = date::floor<date::days>(utc_sys)
  • 为了获取日期,我使用了year_month_day: auto ymd = year_month_day{utc_dp}
  • 而对于时间,我使用了make_time: auto time = make_timeduration_cast<nanoseconds>(utc_sys-utc_dp))
  • 然后我使用了您提到的getter来格式化我的日期和时间。
- Hello_Aiden
这个方法在闰秒期间无效。如果你选择一个处于闰秒期间的 GPS 时间,进行 clock_castsys_time 的转换将会落在闰秒之前最后一个可表示的瞬间。因此日期是正确的,而时间会显示为23:59:59.999... - Howard Hinnant

1
你甚至可以尝试使用 C 时间相关函数的代码。
uint64_t ns = 1281798087485516800ULL + 315964800000000000ULL; // offset between gps epoch and unix epoch is 315964800 seconds

struct timespec ts;
ts.tv_sec = ns / 1000000000ULL;
ts.tv_nsec = ns % 1000000000ULL;

struct tm stm;
gmtime_r(&ts.tv_sec, &stm);
std::cout << stm.tm_year + 1900 << "-" << stm.tm_mon + 1 << "-" << stm.tm_mday << " " << stm.tm_hour << ":" << stm.tm_min << ":" << stm.tm_sec << std::endl;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接