C++20:zoned_time与本地时间组件互转及时间差计算
问题
- 如何在
zoned_time与时间组件(年、月、日、时、分、秒等)之间进行双向转换(需考虑夏令时DST)? - 计算两个
zoned_time之间经过时间的正确方法是什么?
已尝试的操作
可以从时钟获取当前时间生成zoned_time,但对其操作存在困难。参考Stack Overflow代码实现了时间组件提取:
#include <chrono> int main() { using namespace std::chrono; auto tp = zoned_time{current_zone(), system_clock::now()}.get_local_time(); auto dp = floor<days>(tp); year_month_day ymd{dp}; hh_mm_ss time{floor<milliseconds>(tp-dp)}; auto y = ymd.year(); auto m = ymd.month(); auto d = ymd.day(); auto h = time.hours(); auto M = time.minutes(); auto s = time.seconds(); auto ms = time.subseconds(); }
这段代码可获取当前日期并修改时间组件,但无法将修改后的组件转回zoned_time。尝试了以下写法:
auto rec = zoned_time(current_zone(), std::chrono::local_days{y / m / d} + h + M + s + ms); if (tp_s == rec) std::cout << "equal" << std::endl; else { std::cout << "different" << std::endl; std::cout << tp << "\t" << rec << std::endl; }
输出结果:
different
2023-03-21 10:54:30.0128147 2023-03-21 10:54:30.012 GMT
同时对std::chrono::local_days{y / m / d} + h + M + s + ms的含义存在困惑:该加法是时间间隔偏移还是直接设置时间字段?夏令时切换场景下,添加5小时与直接设置小时为5是完全不同的操作。
最后认为计算真实时间跨度的方法是:
auto diff = duration_cast<chr::minutes>(tp_s.get_sys_time() - rec.get_sys_time());
但不清楚以下代码的含义:
auto diff = duration_cast<chr::minutes>(tp_s.get_local_time() - rec.get_local_time());
解答
1. zoned_time与时间组件的双向转换
从zoned_time提取时间组件
你使用的提取代码是完全正确的,核心逻辑如下:
- 调用
zoned_time.get_local_time()获取对应时区的本地时间点(local_time类型) - 通过
floor<days>(tp)截断时间点到日期边界,将结果转换为year_month_day即可提取年、月、日组件 - 计算
tp - dp得到当日的时间偏移量,用hh_mm_ss封装后可提取时、分、秒及亚秒组件
将修改后的组件转回zoned_time
你尝试的local_days{y/m/d} + h + M + s + ms是正确的构造方式,这里的加法是直接设置时间字段,而非时间间隔偏移:
local_days{y/m/d}生成的是指定年月日当天午夜0点的本地时间点- 后续添加的小时、分钟等时长,是直接构造出对应时分秒的本地时间点,等同于直接设置时间字段,和夏令时切换时的时间偏移操作完全不同
至于输出的时间精度差异,是因为原始tp的精度是微秒(10:54:30.0128147),而你构造时仅保留了毫秒精度(hh_mm_ss<milliseconds>的亚秒是3位)。保持精度一致即可解决:
#include <chrono> #include <iostream> int main() { using namespace std::chrono; auto zt = zoned_time{current_zone(), system_clock::now()}; auto tp = zt.get_local_time(); auto dp = floor<days>(tp); year_month_day ymd{dp}; // 用microseconds精度匹配原始时间点 hh_mm_ss time{floor<microseconds>(tp - dp)}; // 修改时间组件示例:设置为当日5点整 auto new_h = hours{5}; auto new_M = minutes{0}; auto new_s = seconds{0}; auto new_subs = microseconds{0}; // 构造新的本地时间点 auto new_local_tp = local_days{ymd.year()/ymd.month()/ymd.day()} + new_h + new_M + new_s + new_subs; auto new_zt = zoned_time{current_zone(), new_local_tp}; std::cout << "原始时间:" << zt << "\n"; std::cout << "修改后时间:" << new_zt << "\n"; }
关于夏令时的处理:zoned_time在构造时会自动处理夏令时带来的特殊情况:
- 夏令时回退时,同一个本地时间可能对应两个系统时间,
zoned_time默认选择较早的那个 - 夏令时前进时,不存在的本地时间会被自动调整为下一个有效时间
2. 计算两个zoned_time的时间跨度
正确方法:使用系统时间计算
你提到的tp_s.get_sys_time() - rec.get_sys_time()是计算真实时间跨度的正确方式:
get_sys_time()返回的是system_clock时间点,对应UTC时间的映射,完全不受夏令时影响,代表物理时间的真实流逝- 两个系统时间点的差值,就是两个时间之间真实经过的时间,结果准确可靠
本地时间差值的含义
tp_s.get_local_time() - rec.get_local_time()计算的是本地时钟显示的时间差,不能代表真实的时间流逝:
- 夏令时前进时,本地时钟跳过1小时,此时两个跨切换点的本地时间差值会比真实时间少1小时
- 夏令时回退时,本地时钟重复1小时,同一个本地时间可能对应两个不同的系统时间,差值可能为0或3600秒,完全无法反映真实时间
因此,这种方法仅适用于计算本地时钟的显示差值,不能用于真实时间跨度的计算。
内容的提问来源于stack exchange,提问作者AndrewBloom

