strftime 安全格式化需用 localtime_s(MSVC)或 localtime_r(POSIX)转换 time_t 为 tm,缓冲区至少 64 字节并检查返回值;put_time 在 MSVC 2015–2019 有严重 bug,生产环境应避免使用。

strftime 是 C 风格、可移植、支持所有编译器;put_time 是 C++11 起的流式方案,但 MSVC 2015–2019 有严重 bug,GCC/Clang 表现正常——别在 Windows 上用 put_time 做生产时间格式化。
strftime 怎么安全地格式化 tm 结构
strftime 依赖 std::tm(必须由 std::localtime 或 std::gmtime 生成),不能直接传 std::time_t。缓冲区大小必须显式控制,否则可能截断或越界。
- 目标缓冲区至少要预留 64 字节(常见格式如
"%Y-%m-%d %H:%M:%S"最长约 20 字符,留余量防扩展) - 务必检查返回值:返回 0 表示缓冲区不足或格式非法,不是成功
- 时区敏感:传入的
tm必须已通过localtime_r(POSIX)或localtime_s(MSVC)安全转换,避免localtime的线程不安全问题
示例:
char buf[64];
std::time_t t = std::time(nullptr);
std::tm lt{};
#ifdef _WIN32
localtime_s(<, &t);
#else
localtime_r(&t, <);
#endif
if (strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", <) == 0) {
// 格式化失败,buf 可能为空或截断
}
put_time 在不同编译器上的实际表现
std::put_time 看起来更“C++”,但落地很脆弱。它本质是 iostream 操控器,依赖 locale 和 facet,而 MSVC 对 time_put facet 的实现长期存在格式丢失、空输出、崩溃等问题(尤其在多线程或非默认 locale 下)。
立即学习“C++免费学习笔记(深入)”;
- GCC 9+ / Clang 10+:基本可用,
std::put_time(&tm, "%F %T")能稳定输出"2024-05-22 14:30:45" - MSVC 2015–2019:即使简单格式如
"%Y"也可能返回空字符串;启用/Zc:__cplusplus也无改善 - MSVC 2022 17.5+:修复部分问题,但仍建议回避——除非你全程控制 locale 且只跑单线程
错误现象典型:std::ostringstream{} 返回空串,且无异常、无日志、不报错。
需要毫秒级或自定义时区怎么办
strftime 和 put_time 都不支持毫秒(%f 是非标准扩展,仅 glibc 支持)、也不处理时区偏移(%z 输出的是本地时区缩写或数字偏移,但无法指定 UTC+08:00 这种格式)。
- 毫秒:先用
std::chrono::system_clock::now()获取time_point,拆出duration后取time_since_epoch().count() % 1000 - UTC+08:00 类型偏移:
strftime的%z输出+0800,需手动插入冒号("+08:00")或用sprintf拼接 - 真正跨时区:别硬啃 C/C++ 标准库,直接上
Howard Hinnant's date library(头文件 only,C++11 起支持),它把zoned_time、sys_time、毫秒、ISO 8601 全封装好了
真要用标准库做高精度或跨平台时间格式化,strftime 是唯一靠谱的选择;put_time 名字好听,实则坑密、兼容性差、调试困难——尤其当你发现日志里某天突然全变成空时间戳时,大概率是它在后台静默失败了。










