如何在libfmt 9.1中按UTC格式化chrono::time_point<system_clock>
我正在使用libfmt 9.1版本,有一个内部以UTC存储的chrono::time_point<chrono::system_clock>时间点,希望输出其时间和日期时不转换为本地时区。
这是libfmt 10版本的默认行为,但在9.1版本中,libfmt会将时间点转换为本地时区输出。示例代码如下:
char e[] = "TZ=:Pacific/Auckland"; putenv(e); tzset(); auto tp = std::chrono::sys_seconds::min(); auto tpStr = fmt::format( "{:%F %T %Z}", tp); std::cout << tpStr << "\n";
使用libfmt v9.1编译时输出1970-01-01 12:00:00 NZST,使用v10编译时输出1970-01-01 00:00:00 GMT。
我无法轻易升级到libfmt v10,担心变更会破坏项目其他部分。我使用C++17,无法使用std::format。请问是否可以让libfmt 9.1按UTC格式化chrono::time_point<chrono::system_clock>?
libfmt文档未提及时区相关内容,std::format文档规定了L前缀,可从默认的C区域设置输出(即libfmt v10的默认行为)切换为本地时区输出。想知道libfmt 9.1是否有反向修饰符?
解决方案
方法1:临时切换时区环境变量
通过临时将进程时区设置为UTC,完成格式化后恢复原时区,实现UTC输出。此方法简单直接,但线程不安全,多线程场景需加锁。
示例代码:
#include <fmt/chrono.h> #include <iostream> #include <cstdlib> #include <cstdio> int main() { // 保存原时区环境变量 const char* original_tz = getenv("TZ"); // 临时设置时区为UTC char utc_tz[] = "TZ=UTC"; putenv(utc_tz); tzset(); // 格式化UTC时间点 auto tp = std::chrono::sys_seconds::min(); auto tpStr = fmt::format("{:%F %T %Z}", tp); std::cout << tpStr << "\n"; // 输出1970-01-01 00:00:00 GMT // 恢复原时区 if (original_tz) { char restore_tz[256]; snprintf(restore_tz, sizeof(restore_tz), "TZ=%s", original_tz); putenv(restore_tz); } else { // 原环境无TZ变量则移除 unsetenv("TZ"); } tzset(); return 0; }
方法2:自定义格式化器(推荐)
编写自定义的fmt格式化器,直接通过gmtime_r(线程安全)将时间点转换为UTC时间,完全控制格式化逻辑,不受libfmt默认时区行为影响。
示例代码:
#include <fmt/chrono.h> #include <fmt/format.h> #include <chrono> #include <ctime> // 自定义UTC格式化器 struct utc_time_formatter { // 支持自定义格式字符串(可选扩展) template <typename ParseContext> constexpr auto parse(ParseContext& ctx) { // 若需要支持自定义格式,可在此解析ctx.format()中的格式说明符 return ctx.begin(); } template <typename FormatContext> auto format(const std::chrono::system_clock::time_point& tp, FormatContext& ctx) { std::time_t t = std::chrono::system_clock::to_time_t(tp); std::tm tm{}; gmtime_r(&t, &tm); // 线程安全的UTC时间转换 // 按指定格式输出,此处可根据需求调整格式 return fmt::format_to(ctx.out(), "{:04d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d} GMT", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); } }; // 注册格式化器,让fmt识别system_clock::time_point的UTC格式化 template <> struct fmt::formatter<std::chrono::system_clock::time_point, char> : utc_time_formatter {}; int main() { char e[] = "TZ=:Pacific/Auckland"; putenv(e); tzset(); auto tp = std::chrono::sys_seconds::min(); auto tpStr = fmt::format("{}", tp); std::cout << tpStr << "\n"; // 输出1970-01-01 00:00:00 GMT return 0; }
如果需要支持自定义格式字符串(如%F %T),可以扩展parse方法解析格式说明符,在format中动态生成输出格式。
方法3:依赖libfmt内部逻辑(不推荐)
libfmt 9.1的chrono格式化内部存在UTC处理逻辑,但未暴露为公共接口。直接调用内部函数会导致代码依赖未公开的实现细节,后续版本升级或维护时容易出问题,仅适合临时应急场景,不建议在生产环境使用。
内容的提问来源于stack exchange,提问作者Lukas Barth

