time_t需用localtime/gmtime转struct tm,二者返回静态指针须立即拷贝;tm字段含偏移(如tm_year=年-1900、tm_mon=0~11);Windows用localtime_s/gmtime_s更安全;tm仅支持秒级精度且无时区语义,高精度或时区操作应选C++20 chrono或date库。

用 localtime 或 gmtime 把时间戳转成 tm 结构体
直接调用 time() 得到的是 time_t 类型的时间戳(秒数),不是结构体。要拿到可读的年月日时分秒字段,必须经过转换。localtime 转成本地时区的 tm,gmtime 转成 UTC 时间。两者都返回 struct tm*,注意返回值是静态分配的指针,多次调用会覆盖前一次结果。
常见错误:直接保存指针后反复使用,比如:
tm* t1 = localtime(&now); tm* t2 = localtime(&later); // t1 指向的内容已被覆盖
正确做法是立即拷贝结构体内容:
- 用
tm my_tm = *localtime(&now);(C++11 起支持结构体赋值) - 或手动 memcpy / 逐字段复制(兼容旧标准)
- 避免跨线程共享同一
tm*返回值
tm 结构体字段含义和易错点
tm 各字段不是“所见即所得”:比如 tm_year 是从 1900 年起算的偏移量,tm_mon 是 0~11(不是 1~12),tm_wday 和 tm_yday 都是从 0 开始计数。这些偏移容易在格式化或计算逻辑中引发 off-by-one 错误。
立即学习“C++免费学习笔记(深入)”;
-
tm_sec:0–61(支持闰秒,但多数系统忽略) -
tm_isdst:-1 表示未知,0 表示非夏令时,>0 表示夏令时;localtime会尝试推断,gmtime总是 0 -
tm_gmtoff和tm_zone不是 POSIX 标准字段,glibc 扩展,跨平台代码不要依赖
Windows 下 localtime_s 和 gmtime_s 的安全替代
MSVC 默认禁用 localtime/gmtime(报 C4996),推荐用带 _s 后缀的安全版本。它们不返回指针,而是把结果写入你提供的 tm 缓冲区,并检查缓冲区大小。
示例:
time_t now;
time(&now);
tm my_tm;
errno_t err = localtime_s(&my_tm, &now);
if (err == 0) {
// 使用 my_tm.tm_hour 等字段
}
- 第二个参数是
const time_t*,别传错地址 - 返回值为 0 表示成功,非 0 是错误码(如
EINVAL) - Linux/macOS 没有
_s版本,需条件编译或封装
需要高精度或纳秒级时间?别用 tm
tm 只能表达秒级精度,且不包含时区信息(仅靠 tm_isdst 和本地规则隐含)。如果需求涉及毫秒/微秒、时区转换、ISO 8601 字符串解析或跨时区比较,tm 就不够用了。
现代替代方案:
- C++20:
<chrono>+std::chrono::zoned_time/sys_time - 第三方:Howard Hinnant 的
date库(C++11/14 兼容) - POSIX:
clock_gettime(CLOCK_REALTIME, &ts)获取struct timespec,再转tm(但丢失时区语义)
用 tm 做时间计算(比如加减天数)很容易出错——它不处理月份长度、闰年、夏令时切换等边界,真要算日期差,优先考虑专门的日期库。


















