C++20起应优先用std::chrono::year_month_day与sys_days计算日期差,避免tm手动解析;C++17及更早需用Howard Hinnant的date库;勿混用时区与日历运算,须分层设计。

用 std::chrono 算两个日期差,别碰 tm 手动解析
标准库从 C++20 开始原生支持日历运算,std::chrono::year_month_day + std::chrono::days 是最直接、最不易出错的方式。手动拆解年月日进 mktime 再减 difftime,不仅跨平台行为不一致(比如时区、夏令时),还容易因 tm 字段未清零导致结果偏差。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 输入字符串先用
std::chrono::parse(C++20)或第三方如date::parse(Howard Hinnant 的 date 库)转成year_month_day - 转成
sys_days(即自 1970-01-01 起的天数),相减得days类型值 - 避免用
time_t或duration_cast,秒级精度在跨闰秒/时区场景下会引入误差
using namespace std::chrono;
auto ymd1 = year_month_day{2023y/3/15};
auto ymd2 = year_month_day{2024y/1/1};
auto d1 = sys_days{ymd1};
auto d2 = sys_days{ymd2};
auto diff = d2 - d1; // days{292}
如果还在用 C++17 或更早,date.h 是唯一靠谱选择
C++17 没有日历类型, 只能处理时间点和持续时间,不能表达“2023 年 2 月 30 日非法”这类语义。此时硬写解析逻辑极易漏掉闰年、大小月、负数年份等边界——尤其当输入含 0000 年或公元前日期时,mktime 直接返回 -1。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 直接集成 Howard Hinnant 的 date library(头文件 only,无需编译)
- 用
date::parse("%Y-%m-%d", ss, ymd)替代sscanf,自动校验日期合法性 - 注意:该库默认使用系统时区,若需 UTC,请显式用
sys_days而非local_days
std::get_time 解析失败?大概率是格式符或流状态没重置
很多人用 std::get_time 配合 std::istringstream 解析日期字符串,但常遇到解析后 ss.fail() 为 true 却查不出原因。根本问题不在格式本身,而在流对象的状态残留或 locale 设置缺失。
常见错误现象:
- 第一次解析成功,第二次调用直接失败 → 忘了
ss.clear()清除failbit - 输入 "2023-02-30" 不报错,反而转成 2023-03-02 →
get_time不做日历校验,只按字段填充tm - 中文环境 locale 下解析失败 → 必须提前
ss.imbue(std::locale::classic())
设计日期类库时,别把“时区”和“日历计算”混在一起
很多自研类库一上来就封装 DateTime,把日期、时间、时区、偏移全塞进一个类。结果是:算两个日期间隔时,不得不强制指定时区(比如 UTC),否则 2023-01-01T00:00+08:00 和 2023-01-01T00:00-05:00 被当成不同时间点,哪怕它们代表同一物理时刻。
合理分层建议:
- 底层:纯日历类型(
year_month_day、year_month_weekday)——无时区、无精度损失 - 中层:带时区的时间点(
zoned_time)——用于显示、调度、跨时区转换 - 上层:业务实体(如
BookingDate)——只存year_month_day,避免隐式时区假设
真正难的不是加减天数,而是让开发者一眼看出某个变量是否携带时区语义。一旦混淆,测试用例在纽约跑过,在东京就错。










