最安全方式是用 std::chrono::system_clock::now() 减去当天零点(c++20 用 floor,c++17 及以前手动计算),得到纯时间偏移量 duration,再拆解为时分秒毫秒;避免 strftime/put_time 的时区、线程安全与精度问题。

用 std::chrono 提取纯时间(时分秒毫秒)
直接丢弃日期部分最安全的方式是用 std::chrono::system_clock::now() 获取当前时间点,再减去当天零点的时间点,得到当天已过去的持续时间。这样拿到的是一个 std::chrono::duration,天然不含日期信息。
关键不是“去除”,而是“只取时间偏移量”——这避免了时区转换、闰秒、本地化格式等干扰。
-
std::chrono::system_clock::now()返回的是 UTC 时间点(实际是自纪元起的纳秒数,但精度由实现决定) - 需用
std::chrono::floor<:chrono::days>(tp)</:chrono::days>截断到当天零点(C++20 起推荐),若用 C++17 或更早,可用std::chrono::duration_cast<:chrono::days>(tp.time_since_epoch())</:chrono::days>手动算零点 - 相减后得到
std::chrono::duration,再按需转成hours/minutes/seconds/milliseconds
auto now = std::chrono::system_clock::now(); auto today = std::chrono::floor<:chrono::days>(now); auto time_of_day = now - today; // 类型是 std::chrono::nanoseconds(或更低精度) auto h = std::chrono::duration_cast<:chrono::hours>(time_of_day).count(); auto m = std::chrono::duration_cast<:chrono::minutes>(time_of_day % std::chrono::hours(1)).count(); auto s = std::chrono::duration_cast<:chrono::seconds>(time_of_day % std::chrono::minutes(1)).count(); auto ms = std::chrono::duration_cast<:chrono::milliseconds>(time_of_day % std::chrono::seconds(1)).count(); </:chrono::milliseconds></:chrono::seconds></:chrono::minutes></:chrono::hours></:chrono::days>
用 std::strftime 格式化为字符串(仅时分秒)
如果目标是生成类似 "14:23:59" 或 "14:23:59.123" 这样的字符串,且不关心后续计算,std::strftime 更直接,但必须经过 std::localtime 或 std::gmtime 转换——这意味着它依赖系统时区设置,且会引入本地化行为(比如某些 locale 下分钟/秒前补空格而非零)。
- 务必用
std::gmtime避免时区干扰;若需本地时间,确保你清楚std::localtime的线程不安全性(C++20 前无std::localtime_s) - 格式符:
"%H:%M:%S"得到时分秒(24 小时制),"%T"是等价简写;毫秒需手动提取(std::chrono方案更准) - 缓冲区大小至少 9 字节(
"HH:MM:SS\0"),加毫秒则需更多
auto now = std::chrono::system_clock::now();
auto tt = std::chrono::system_clock::to_time_t(now);
std::tm tm_buf{};
#ifdef _WIN32
gmtime_s(&tm_buf, &tt);
#else
gmtime_r(&tt, &tm_buf); // Linux/macOS
#endif
char buf[16];
std::strftime(buf, sizeof(buf), "%H:%M:%S", &tm_buf);
// buf 现在是类似 "14:23:59"
为什么不用 std::put_time 直接格式化?
std::put_time 看似简洁,但它底层仍调用 std::strftime,且必须传入 std::tm*。问题在于:它无法处理毫秒,也不接受 std::chrono::duration;更重要的是,它默认使用 std::localtime,而该函数在多线程下可能返回指向静态缓冲区的指针,导致数据被覆盖。
- 若强制用
std::put_time,必须自己保证std::tm对象生命周期,且显式调用std::gmtime_r或std::gmtime_s - 它不支持毫秒,哪怕你把毫秒塞进
tm的tm_sec字段也没用——std::put_time完全忽略非标准字段 - 对齐和填充行为受 locale 影响(例如某些 locale 下
%M可能输出" 7"而非"07")
C++17 以下如何兼容处理零点截断?
C++20 引入了 std::chrono::floor<:chrono::days></:chrono::days>,干净利落;C++17 及之前需手动算当天零点,容易出错——尤其跨月/跨年时误用 std::chrono::hours(24) 而非 std::chrono::days(1)(后者考虑闰日)。
- 正确做法:用
std::chrono::duration_cast<:chrono::days>(tp.time_since_epoch())</:chrono::days>得到整数天数,再乘回std::chrono::days(1)构造零点时间点 - 错误示例:
tp - std::chrono::hours(24 * (tp / std::chrono::hours(24)).count())——hours不能精确表示一天(夏令时切换时可能为 23 或 25 小时) - 若项目已用
date.h(Howard Hinnant 的库),可直接用date::floor<:days>(tp)</:days>,语义清晰且跨标准兼容
毫秒级精度要求高时,别依赖 time_t 或 struct tm;它们通常只到秒级。真正需要毫秒字符串,还是得从 std::chrono::system_clock::now() 出发,先算差值,再拆解。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











