用 std::chrono::sys_days 计算日历日期差最稳妥,避免 time_t 手动计算的时区、闰秒、夏令时等陷阱;需校验 year_month_day 有效性,注意差值是否包含首尾日。

用 std::chrono 算两个日期差,别碰 tm 和 time_t 手动算
直接用 std::chrono 配合 std::chrono::sys_days 最稳。手动解析年月日再转 time_t,会掉进时区、闰秒、夏令时、1970年前日期全挂掉的坑里。
常见错误现象:difftime 返回负数但日期明明是正向的;1900年2月29日算出来是合法日期;跨年时天数差少一天。
- 只处理“日历日期”(年-月-日),不带时分秒,就用
std::chrono::year_month_day - 构造后转成
std::chrono::sys_days—— 它本质是自1970-01-01以来的天数,支持减法 - 注意:
year_month_day{2024y/2/29}是合法对象,但调用.to_sys()会抛std::out_of_range,得先校验
auto d1 = std::chrono::year_month_day{2023y/12/1};
auto d2 = std::chrono::year_month_day{2024y/3/15};
auto sd1 = std::chrono::sys_days{d1}; // 自动校验日期有效性
auto sd2 = std::chrono::sys_days{d2};
auto diff = sd2 - sd1; // 类型是 std::chrono::days
std::cout << diff.count() << " days\n"; // 输出 105
遇到字符串输入(如 "2023-06-15")怎么安全转成日期
别用 strptime 或手写 sscanf——没内置校验,"2023-13-01" 也能过,结果是未定义行为。
使用 std::chrono::parse(C++20)是最简方案,失败时抛异常,可捕获处理。
立即学习“C++免费学习笔记(深入)”;
- 格式字符串必须严格匹配,
"%Y-%m-%d"不能写成"%Y/%m/%d" - 输入含空格或多余字符会导致解析失败,建议先
trim字符串 - 如果编译器不支持 C++20,老办法是用
std::get_time+std::tm,但务必检查tm_year和tm_mday是否在合理范围
std::string s = "2023-06-15";
std::chrono::sys_days tp;
std::istringstream ss{s};
ss >> std::chrono::parse("%Y-%m-%d", tp);
if (ss.fail()) {
throw std::runtime_error("invalid date format");
}
计算差值时为什么有时多一天或少一天
核心原因:你用的起点和终点是否包含“当天”。sys_days 相减是纯日历天数差,比如 2023-01-01 到 2023-01-02 是 1 天,不是 2 天。
容易踩的坑:
- 业务上说“从 A 日到 B 日共几天”,可能指包含首尾的天数(即差值+1),得看需求定
- 若输入是带时间的字符串(如 "2023-01-01T14:00"),用
sys_days会截断时间部分,导致结果偏移 - 跨时区比较时,
sys_days基于 UTC,但用户输入可能是本地时间,没做转换就会错
Windows 上 std::chrono::parse 不工作?
MSVC 19.3x 及更早版本对 std::chrono::parse 支持不完整,尤其对中文 locale 或非 ASCII 分隔符易崩溃。不是你代码错,是标准库 bug。
临时绕过方式:
- 强制用
std::locale{"C"}再试一次 - 降级用
std::get_time,但记得把年份加 1900、月份加 1(tm_mon是 0-based) - 确认编译选项开了
/std:c++20,且没定义_HAS_AUTO_PTR_ETC这类禁用新特性的宏
真正麻烦的是:日期逻辑一旦混入本地化、时区、模糊输入(如 "last Monday"),C++ 标准库就力不从心了。这时候该上 date.h(Howard Hinnant 的第三方库),它补全了所有缺失环节,而且 API 一脉相承。











