必须清零tm结构体未设置字段,否则mktime会用随机值导致结果错误;需显式初始化所有字段(如tm_hour=tm_min=tm_sec=0)、正确设置tm_year(减1900)和tm_mon(0~11),并检查mktime返回值是否为-1。

用 mktime 算日期差前,必须先清零 tm 结构体的未设置字段
直接填充 tm_year、tm_mon、tm_mday 后调用 mktime,结果可能错得离谱。因为 tm 里还有 tm_hour、tm_min、tm_sec 等字段——如果没显式赋值,它们是栈上的随机垃圾值,mktime 会拿这些值参与时间计算,导致秒数偏差极大(比如多算几十年)。
实操建议:
- 始终用
memset(&tm_struct, 0, sizeof(tm_struct))或tm_struct = {}初始化整个结构体 -
tm_year填年份减 1900(如 2024 → 124),tm_mon填 0~11(1月是 0),tm_mday填 1~31(注意别越界) - 即使只关心日期,也显式设
tm_hour = tm_min = tm_sec = 0,更直观、不易漏
mktime 返回 -1 不一定代表日期非法,可能是时区或系统限制
常见错误现象:输入合法日期(如 1970-01-01)却返回 -1;或者在某些嵌入式平台/旧系统上,早于 1970 或晚于 2038 的日期直接失败。
原因和应对:
立即学习“C++免费学习笔记(深入)”;
-
mktime将本地时间转为time_t(通常是自 1970-01-01 UTC 起的秒数),受time_t位宽和系统实现约束(32 位系统上限是 2038-01-19) - 若
tm_isdst未设(默认 -1),mktime会尝试自动判断夏令时,但在边界日期(如 DST 切换日)可能失败;稳妥做法是设tm_isdst = 0(禁用夏令时) - 检查返回值:若为 -1,用
errno辅助诊断(如EOVERFLOW表示超出范围,EINVAL表示字段非法)
两个 time_t 相减得到的是秒差,转天数要整除 86400 且注意符号
直接 (t2 - t1) / 86400 看似合理,但有陷阱:
- 如果
t2 < t1,结果是负数,除法向零截断(C++11 起),但多数场景需要绝对天数差,应先取差再用llabs()(避免long溢出) - 86400 是常量,建议定义为
constexpr long SECONDS_PER_DAY = 24L * 3600L,避免硬编码 - 注意:
time_t可能是 32 位,大跨度日期相减可能溢出;推荐用std::difftime(t2, t1)(返回double,精度足够且安全)
示例关键片段:
tm t1 = {}, t2 = {};
t1.tm_year = 2024 - 1900; t1.tm_mon = 5; t1.tm_mday = 15;
t2.tm_year = 2024 - 1900; t2.tm_mon = 6; t2.tm_mday = 20;
t1.tm_isdst = t2.tm_isdst = 0;
<p>time_t sec1 = mktime(&t1), sec2 = mktime(&t2);
if (sec1 == -1 || sec2 == -1) { /<em> 错误处理 </em>/ }
double diff_sec = difftime(sec2, sec1);
long days = static_cast<long>(diff_sec / 86400.0); // 向零取整,符合日常“差几天”直觉</p>跨年/跨月计算时,别依赖 tm 字段自动归一化
mktime 确实会把 tm_mon=13 这类非法值自动折算成下一年 1 月,但这是副作用,不是设计目的。一旦你写 t.tm_mon = 12 期望它变成“下一年 1 月”,代码可读性差,且容易在调试时误判逻辑。
更可靠的做法:
- 所有输入日期先手动校验合法性(如月份 0~11、日期 1~28/29/30/31)
- 需要加减天数时,不要改
tm再调mktime,而是用time_t加减秒数后,再用localtime转回tm - 复杂日期运算(如“某月最后一天”“N 个工作日后”)建议用
<chrono>+std::year_month_day(C++20),tm+mktime仅适合简单、确定的日期差
真正难的从来不是怎么调 mktime,而是怎么让 tm 里的每个字段都处在你明确控制的状态下——哪怕只是填个 0,也要亲手填。


















