C++put_time格式化输出时间

2026-04-10 19:50:33 558阅读 0评论

C++里时间格式化,别再手撕字符串了:put_time 真香现场

上周帮同事改一段日志代码,他用 strftime 配合 std::tmstd::time_t,再手动拼 std::string,中间还漏了 std::locale("") 导致中文系统下星期几显示成英文缩写——结果日志里“Mon”“Wed”混在一堆中文里,像极了凌晨三点没睡醒的自己。

其实从 C++11 起,标准库就悄悄塞进了一个轻量又靠谱的工具:std::put_time。它不造轮子,不绕弯子,专治“想把时间按自己心意打出来”这回事。


put_time 不是函数,是个流操纵器(stream manipulator),必须和 std::ostream(比如 std::coutstd::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,再用 localtimegmtimetm

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::setwstd::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 的那一行代码贴过去——省下的调试时间,够泡一杯咖啡了。

文章版权声明:除非注明,否则均为Dark零点博客原创文章,转载或复制请以超链接形式并注明出处。

发表评论

快捷回复: 表情:
验证码
评论列表 (暂无评论,558人围观)

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

目录[+]