GCC 14之前版本及Clang 20.1及以下版本中std::chrono::from_stream缺失的替代方案咨询
我之前在维护跨平台项目时也碰到过这个问题——老版本的GCC和Clang确实没实现C++20里的std::chrono::from_stream(),尤其是需要构造time_point的时候,确实有点头疼。不过既然你已经能把日期时间的各个字段(年、月、日、时、分、秒、UTC偏移)解析出来了,其实有几个不用额外依赖、还比较优雅的替代方案:
方案一:用C++20 Chrono日历组件构造(推荐)
虽然from_stream()没实现,但很多老版本编译器(比如GCC 11+、Clang 12+)已经支持C++20 Chrono的日历和时区基础组件了,比如year_month_day和sys_days。你可以用这些组件直接拼接出时间点,再调整UTC偏移:
#include <chrono> // 假设你已经解析出以下变量: int year = 2018, month = 12, day = 9; int h = 0, m = 0, s = 0; int offset_h = 1, offset_m = 30; // 对应+0130的UTC偏移 namespace chr = std::chrono; // 构造年-月-日对象,先校验合法性 auto ymd = chr::year(year)/chr::month(month)/chr::day(day); if (!ymd.ok()) { // 处理无效日期(比如2月30号) return; } // 从日期转换为system_clock的天数时间点,再加上当天的时分秒 auto sys_tp = chr::sys_days(ymd) + chr::hours(h) + chr::minutes(m) + chr::seconds(s); // 调整UTC偏移:如果输入的是带偏移的本地时间,转成UTC需要减去偏移量 auto offset = chr::hours(offset_h) + chr::minutes(offset_m); auto utc_tp = chr::clock_cast<chr::utc_clock>(sys_tp - offset); // 现在utc_tp就是你需要的std::chrono::utc_clock::time_point了
这个方案完全用现代C的Chrono组件,没有C API的线程安全问题,也不需要额外依赖,只要你的编译器支持基础的C20 Chrono特性就行。
方案二:手动计算时间点(兼容更老的编译器)
如果你的编译器连year_month_day都不支持,那可以手动计算从Unix纪元(1970-01-01 UTC)到目标日期的总秒数,再加上当天的时分秒和偏移调整:
#include <chrono> // 辅助函数:计算从1970-01-01到目标日期的天数 bool is_leap_year(int y) { return (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0); } int days_since_epoch(int year, int month, int day) { int days = 0; // 计算整年份的天数 for (int y = 1970; y < year; ++y) { days += is_leap_year(y) ? 366 : 365; } // 计算当年到目标月份的天数 const int month_days[] = {31,28,31,30,31,30,31,31,30,31,30,31}; for (int m = 1; m < month; ++m) { days += month_days[m-1]; if (m == 2 && is_leap_year(year)) { days += 1; } } // 加上当月的天数 days += day - 1; return days; } // 构造时间点 int year = 2018, month = 12, day = 9; int h = 0, m = 0, s = 0; int offset_h = 1, offset_m = 30; auto total_days = days_since_epoch(year, month, day); // 计算总秒数:天数*86400 + 时分秒,再减去UTC偏移 long long total_seconds = total_days * 86400LL + h*3600 + m*60 + s; total_seconds -= (offset_h*3600 + offset_m*60); // 构造utc_clock的time_point using namespace std::chrono; auto utc_tp = utc_clock::time_point(seconds(total_seconds));
这个方案完全不依赖C20的新特性,兼容几乎所有C11及以上的编译器,但需要自己处理闰年和月份天数的逻辑,要注意边界情况(比如1970年之前的日期,不过一般业务场景很少用到)。
方案三:安全使用C API(迫不得已时)
如果你实在想用from_time_t(),可以避免线程不安全的localtime()/gmtime(),而是手动填充std::tm结构,再用线程安全的API转换为time_t:
#include <chrono> #include <ctime> // 兼容POSIX和Windows的UTC时间转换函数 #ifdef _WIN32 #define TIME_GM _mkgmtime #else #define TIME_GM timegm #endif int year = 2018, month = 12, day = 9; int h = 0, m = 0, s = 0; int offset_h = 1, offset_m = 30; std::tm tm{}; tm.tm_year = year - 1900; // tm_year是从1900年开始的差值 tm.tm_mon = month - 1; // tm_mon是0-11的范围 tm.tm_mday = day; tm.tm_hour = h; tm.tm_min = m; tm.tm_sec = s; tm.tm_isdst = 0; // 强制不使用夏令时,因为我们处理的是UTC相关时间 // 转换为UTC的time_t time_t t = TIME_GM(&tm); if (t == -1) { // 处理转换失败 return; } // 构造system_clock的time_point,再调整UTC偏移并转换为utc_clock using namespace std::chrono; auto sys_tp = system_clock::from_time_t(t); sys_tp -= hours(offset_h) + minutes(offset_m); auto utc_tp = clock_cast<utc_clock>(sys_tp);
这个方案依赖C标准库,但我们手动填充std::tm,避免了调用localtime()这类线程不安全的函数,相对安全一些。不过要注意TIME_GM的跨平台兼容问题。
内容来源于stack exchange

