最可靠方式是用std::chrono将毫秒转system_clock::time_point,再经time_t转本地tm,最后用std::put_time格式化;需避免steady_clock、精度截断及线程不安全的localtime。

用 std::chrono 解析毫秒值再格式化输出最可靠
直接对 long 毫秒数做除法拆解年月日容易出错(比如忽略闰年、时区、夏令时),也难以适配本地化需求。C++11 起推荐走 std::chrono + std::format(C++20)或 std::put_time(C++11)路径,本质是把毫秒转成 std::time_point,再交给标准库格式化。
关键步骤:毫秒 → std::chrono::milliseconds → std::chrono::system_clock::time_point → std::tm → 格式化字符串
常见错误:误用 std::chrono::steady_clock(它不对应日历时间)、忽略 system_clock::from_time_t 的精度截断、忘记把 time_t 转为本地 std::tm 就直接格式化(导致 UTC 时间)。
用 std::put_time(C++11)兼容老项目
适用于不能升级到 C++20 的环境,需手动转换时区(默认本地时间)。注意 std::localtime 是线程不安全的,多线程下应改用 std::localtime_r(POSIX)或 std::gmtime_s(MSVC)。
立即学习“C++免费学习笔记(深入)”;
-
long ms = 1717025489123L;// 示例毫秒时间戳 - 构造
std::chrono::milliseconds:auto dur = std::chrono::milliseconds(ms); - 转为
system_clock::time_point:auto tp = std::chrono::system_clock::time_point(dur); - 转
time_t:std::time_t t = std::chrono::system_clock::to_time_t(tp); - 转本地
std::tm(线程安全写法示例):std::tm tm_buf {}; #ifdef _WIN32 localtime_s(&tm_buf, &t); #else localtime_r(&t, &tm_buf); #endif - 格式化:
std::ostringstream oss; oss
用 std::format(C++20)更简洁且类型安全
避免 std::ostringstream 和临时缓冲区,支持编译期格式检查。但需确认编译器支持(GCC 13+、Clang 15+、MSVC 19.32+),且 std::format 对 std::tm 的支持依赖于标准库实现,部分旧版 libc++ 可能不完整。
- 仍需先得到
std::tm(步骤同上) - 直接格式化:
std::string s = std::format("{:%Y-%m-%d %H:%M:%S}", tm_buf); - 若要带毫秒字段(如
123),需额外拼接:std::format("{:%Y-%m-%d %H:%M:%S}.{:03}", tm_buf, ms % 1000) - 注意:
std::format不自动处理时区,tm_buf必须已是本地时间
别忽略时区和纪元起点
C++ system_clock 通常以 Unix 纪元(1970-01-01 00:00:00 UTC)为起点,但标准未强制要求。极少数嵌入式平台可能不同,此时 system_clock::time_since_epoch() 返回值会异常。生产环境建议加校验:
- 检查毫秒值是否合理:比如
ms 32536799999999LL(约公元10000年)可初步过滤明显错误 - 若需 UTC 时间而非本地时间,用
std::gmtime_r替代localtime_r,并用std::put_time配合"%Y-%m-%d %H:%M:%S UTC" - 跨平台分发时,
std::put_time的格式符行为在 Windows 和 glibc 上基本一致,但某些嵌入式 libc(如 newlib)可能不支持全部修饰符
毫秒转字符串真正麻烦的从来不是格式化本身,而是“这个毫秒到底对应哪个时区下的哪一天”——搞清输入来源(是 JS Date.now()?还是 Java System.currentTimeMillis()?),比选函数更重要。


















