C++put_time格式化输出时间
C++里时间格式化,别再手撕字符串了:put_time 真香现场
上周帮同事改一段日志代码,他用 strftime 配合 std::tm 和 std::time_t,再手动拼 std::string,中间还漏了 std::locale("") 导致中文系统下星期几显示成英文缩写——结果日志里“Mon”“Wed”混在一堆中文里,像极了凌晨三点没睡醒的自己。
其实从 C++11 起,标准库就悄悄塞进了一个轻量又靠谱的工具:std::put_time。它不造轮子,不绕弯子,专治“想把时间按自己心意打出来”这回事。
put_time 不是函数,是个流操纵器(stream manipulator),必须和 std::ostream(比如 std::cout 或 std::ostringstream)一起用。它的核心就一句:
std::cout << std::put_time(&tm, "%Y-%m-%d %H:%M:%S");
注意两个硬性前提:
✅ 必须传入 const std::tm* 指针(不是值,也不是 std::chrono::system_clock::time_point);
✅ 格式字符串用的是 POSIX strftime 规范,不是 printf 那套,也不是自创语法。
所以第一步永远绕不开:把现代时间类型转成 std::tm。
比如你拿到的是 std::chrono::system_clock::now(),得先转 time_t,再用 localtime 或 gmtime 转 tm:
auto now = std::chrono::system_clock::now();
std::time_t t = std::chrono::system_clock::to_time_t(now);
std::tm tm_buf {};
localtime_r(&t, &tm_buf); // Linux/macOS 推荐带 _r 版本,线程安全
// Windows 用 localtime_s(&tm_buf, &t);
这里有个容易踩的坑:*别直接 `std::tm tm = std::localtime(&t)**。std::localtime返回静态缓冲区指针,多线程下会互相覆盖。_r或_s` 是刚需,不是可选项。
格式化本身很干净,但细节决定输出是否“顺眼”。比如:
%Y是 4 位年份(2024),%y是 2 位(24);%H是 24 小时制(00–23),%I是 12 小时制(01–12),别和%p(AM/PM)漏配;%Z输出时区缩写(CST、PDT),但依赖当前 locale——默认"C"locale 下它常为空;- 想让月份名、星期名显示中文?得显式设置 locale:
std::locale loc("zh_CN.UTF-8"); // Linux/macOS
// std::locale loc(".936"); // Windows 简体中文
std::cout.imbue(loc);
std::cout << std::put_time(&tm_buf, "%Y年%m月%d日 %A %H:%M:%S") << '\n';
// 输出示例:2024年04月12日 星期五 15:32:18
注意:imbue() 要在 put_time 前调用,且影响整个流后续所有带 locale 敏感的操作(比如数字千分位)。如果只是局部需要中文时间,建议用 std::ostringstream 隔离:
std::ostringstream oss;
oss.imbue(std::locale("zh_CN.UTF-8"));
oss << std::put_time(&tm_buf, "%B %d, %Y");
std::string result = oss.str(); // "四月 12, 2024"
有人问:“put_time 能处理毫秒吗?”
不能。std::tm 本身不存毫秒,put_time 只读 tm 结构体字段。
但你可以手动补:
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
now.time_since_epoch()) % 1000;
std::cout << std::put_time(&tm_buf, "%H:%M:%S") << '.'
<< std::setfill('0') << std::setw(3) << ms.count() << '\n';
// 输出:15:32:18.127
这里用了 std::setw 和 std::setfill 配合,比拼接字符串更可控。
最后说个实用技巧:封装一个可复用的时间输出函数,避免每次重复写 tm 转换:
template<typename Clock, typename Duration>
std::string format_time(const std::chrono::time_point<Clock, Duration>& tp,
const char* fmt,
const std::locale& loc = std::locale("")) {
auto t = Clock::to_time_t(tp);
std::tm tm_buf {};
localtime_r(&t, &tm_buf);
std::ostringstream oss;
if (loc != std::locale("")) oss.imbue(loc);
oss << std::put_time(&tm_buf, fmt);
return oss.str();
}
// 用起来清爽多了:
auto now = std::chrono::system_clock::now();
std::cout << format_time(now, "%F %T") << '\n'; // 2024-04-12 15:32:18
std::cout << format_time(now, "%Y年%m月%d日", std::locale("zh_CN.UTF-8")) << '\n';
put_time 不是万能钥匙,但它把“格式化时间”这件事拉回了标准库该有的样子:不依赖第三方、不手撕字符、不猜编码。它不炫技,只解决一个具体问题——把时间,按你想看的样子,稳稳当当印出来。
下次再看到日志里乱码的星期几,或者同事还在用 sprintf 拼时间字符串,不妨把 put_time 的那一行代码贴过去——省下的调试时间,够泡一杯咖啡了。


还没有评论,来说两句吧...