time_t在1970年前通常不可靠,因mktime()、localtime()等函数在Windows和旧glibc上对早于1970年的日期静默失败;推荐用C++20的std::chrono::sys_days或C++11兼容的date.h库替代。

time_t 在 1970 年前通常不可靠
绝大多数 C++ 标准库实现(尤其是基于 POSIX 的)把 time_t 定义为自 1970-01-01 00:00:00 UTC 起的秒数,且默认使用有符号整型。这意味着理论上它能表示 1970 年前的时间——但实际中大量函数会出问题:mktime()、localtime()、gmtime() 在很多平台(特别是 Windows MSVC CRT 和旧版 glibc)对早于 1970 年的 struct tm 输入直接返回 nullptr 或错误值,不报明确错误,只静默失败。
常见现象包括:
-
mktime(&tm)返回-1,但tm.tm_year设为70(即 1970)以下时未必触发 errno -
localtime(&time_val)返回空指针,而time_val是一个负的time_t - 跨平台构建时,Linux(较新 glibc)可能勉强支持到 1901 年,Windows 则基本卡死在 1970
用 std::chrono::sys_days 绕过 time_t 限制
C++20 引入的 std::chrono::sys_days 是目前最干净的替代方案:它不依赖 time_t,底层用 std::int64_t 存天数,基准是 1970-01-01,但支持远至约 ±30 万年的日期(±10⁸ 天)。只要编译器支持 C++20 且标准库完整(libstdc++ ≥12、libc++ ≥15、MSVC ≥19.30),就能安全处理 1900 年甚至更早的日期。
实操要点:
立即学习“C++免费学习笔记(深入)”;
- 构造用
std::chrono::year_month_day{y, m, d},例如std::chrono::year_month_day{1848y, 2m, 24d} - 转为纪元天数:调用
.time_since_epoch().count(),得到从 1970-01-01 起的天数(可为负) - 避免用
std::chrono::system_clock::from_time_t(),它仍走time_t路径 - 格式化输出需配合
<chrono>的format()(C++20)或手动拼接
示例:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
using namespace std::chrono;
auto ymd = year_month_day{1789y, 7m, 14d};
sys_days dp{ymd};
std::cout << dp.time_since_epoch().count() << "\n"; // 输出 -66472(即 1789-07-14 距 1970-01-01 的天数)
兼容 C++11/14/17 的 fallback 方案
若无法升级到 C++20,推荐用轻量第三方库 date.h(Howard Hinnant 提供,头文件仅、无依赖、C++11 兼容)。它提供了和 C++20 <chrono> 几乎一致的 API,且对 pre-1970 支持更成熟。
关键差异与注意点:
- 头文件是
"date/date.h",不是系统自带的;需显式下载并包含 -
date::sys_days行为与 C++20 完全一致,可无缝迁移 - 解析字符串用
date::parse(),比手写strptime()更可靠(后者在 macOS 和 Windows 上对 pre-1970 支持极差) - 不要混用
date::sys_days和std::chrono::system_clock::time_point,转换需经.time_since_epoch()
示例(C++14):
#include "date/date.h"
auto tp = date::sys_days{date::year{1600}/1/1};
std::cout << date::format("%Y-%m-%d", tp) << "\n"; // 输出 1600-01-01
为什么 strftime / strptime 不该用于 pre-1970 输入
strftime() 本身只是格式化,看似安全,但它依赖传入的 struct tm* —— 而这个结构体几乎总是由 localtime() 或 gmtime() 生成,这两个函数正是 pre-1970 的主要故障点。strptime() 更危险:glibc 版本在解析 1900 年前日期时可能触发未定义行为;macOS 的 strptime() 对年份 strptime()。
所以真正的问题不在格式化函数本身,而在上游时间对象的构建链。一旦你绕过了 time_t → tm 这一环(比如用 date::year_month_day 或 std::chrono::year_month_day 直接构造),后续格式化就可控了。
容易被忽略的一点:即使你用 date.h 解析了 1700 年的日期,若后续仍调用 mktime() 把它转成 time_t 再传给 strftime(),就又掉回坑里了。整个链路必须全程避开 time_t 和 C 风格时间函数。

















