最简方式是先用localtime_r/gmtime_r将tv_sec转struct tm,再拼接tv_usec;需校验tv_usec范围并按意图选本地/UTC时区函数。

timeval 转 std::string 最简方式
直接用 std::put_time 不行,因为 struct timeval 不是 std::tm;必须先转成 time_t + 微秒部分单独处理。
最常用且安全的做法是用 localtime_r(Linux/macOS)或 localtime_s(Windows)把 tv_sec 转成 struct tm,再拼接微秒:
#include <chrono>
#include <ctime>
#include <iostream>
#include <sstream>
std::string format_timeval(const struct timeval& tv) {
struct tm tm_buf;
#ifdef _WIN32
localtime_s(&tm_buf, &tv.tv_sec);
#else
localtime_r(&tv.tv_sec, &tm_buf);
#endif
std::ostringstream oss;
oss << std::put_time(&tm_buf, "%Y-%m-%d %H:%M:%S");
oss << '.' << std::setfill('0') << std::setw(6) << tv.tv_usec;
return oss.str();
}
避免时区错误的关键点
timeval.tv_sec 是 UTC 时间戳,但 localtime_* 默认按本地时区解析——如果你要输出 UTC 时间,得换 gmtime_r / gmtime_s。
- 输出本地时间:用
localtime_r(POSIX)或localtime_s(MSVC) - 输出 UTC 时间:必须用
gmtime_r或gmtime_s,否则时间会偏移 - 不加锁的
localtime在多线程下不安全,别用
用 std::format(C++20)更简洁但要注意兼容性
C++20 的 std::format 不能直接格式化 timeval,仍需拆解。但它能简化字符串拼接:
立即学习“C++免费学习笔记(深入)”;
// C++20,需编译器支持(GCC 13+ / Clang 15+ / MSVC 19.32+)
auto t = std::chrono::system_clock::from_time_t(tv.tv_sec);
auto us = std::chrono::microseconds(tv.tv_usec);
auto tp = t + us;
auto tms = std::chrono::time_point_cast<std::chrono::milliseconds>(tp);
std::string s = std::format("{:%Y-%m-%d %H:%M:%S}.{:03}",
std::chrono::floor<std::chrono::seconds>(tp),
tms.time_since_epoch().count() % 1000);
注意:std::format 对微秒精度支持有限,上面示例只保留毫秒;要完整 6 位微秒,还得手动补零或用 std::ostringstream。
常见错误:tv_usec 超出范围导致输出错乱
tv_usec 理论上应在 [0, 999999] 区间,但某些系统或手动生成的 timeval 可能越界(比如 1234567),直接打印会显示 “1.234567 秒” 这种非法值。
- 务必校验:
if (tv.tv_usec < 0 || tv.tv_usec >= 1000000) - 修正方式:进位到
tv_sec,再取模:tv.tv_sec += tv.tv_usec / 1000000; tv.tv_usec %= 1000000; - 尤其在跨平台接收网络时间戳、或用
gettimeofday+ 手动计算时容易踩坑
tv_usec 的合法性检查和时区意图是否明确——这两点漏掉一个,日志就可能对不上监控指标。


















