判断 std::tm 是否表示年份第一天只需检查 tm_mday == 1 && tm_mon == 0,因 tm_mon 从 0 开始(0 为一月),tm_mday 从 1 开始(1 为当月首日);tm_year 不参与判断,且需确保手动构造时 tm_mon 在 [0,11] 范围内。

判断 std::tm 是否表示年份第一天
直接看 tm_mday == 1 && tm_mon == 0 就够了。C++ 的 std::tm 中,tm_mon 是从 0 开始的(0 表示一月),tm_mday 是从 1 开始的(1 表示当月第一天)。所以“该年第一天”就是一月一日,对应 tm_mon == 0 且 tm_mday == 1。
注意:tm_year 是自 1900 年起的偏移量(如 2024 年对应 tm_year == 124),但它不参与“是否为年初”的判断——只要月份和日期对,就是年初,不管年份值是否合法或已归一化。
- 如果数据来自
std::localtime或std::gmtime,它们返回的tm已归一化,可直接用tm_mday和tm_mon - 如果手动构造
std::tm(比如解析字符串后填值),务必确保tm_mon范围是 [0,11],否则tm_mon == 0可能是无效输入而非真的一月 -
tm_yday(一年中的第几天)理论上也能用:tm_yday == 0表示年初。但该字段在未调用mktime前可能未填充或为 -1,不可靠
从字符串解析后验证是否为年初
常见场景:读取 "2024-01-01" 这类 ISO 格式字符串,想快速确认是不是当年第一天。别急着转 std::time_t,先做轻量级文本校验更高效。
对形如 "YYYY-MM-DD" 的字符串(长度固定为 10,含两个短横),只需检查:
- 前 4 位是数字(年份)
- 第 5–6 位是
"01" - 第 8–9 位是
"01"
例如:"2024-01-01" ✅,"2024-01-02" ❌,"2024-12-01" ❌。这种检查不依赖 locale、不调用库函数、无解析开销,适合高频校验。
若需兼容其他格式(如 "1/1/2024"),就老实用 std::get_time 解析到 std::tm,再按前一节方式判断。
用 std::chrono::year_month_day 判断(C++20)
C++20 引入了类型安全的日期类型,std::chrono::year_month_day 是最直观的选择。构造后直接比对:
auto ymd = std::chrono::year_month_day{std::chrono::sys_days{tp}};
if (ymd.month() == std::chrono::January && ymd.day() == std::chrono::day{1}) { /* 是年初 */ }
要点:
- 必须先将时间点
tp(如std::chrono::system_clock::time_point)转成std::chrono::sys_days,再隐式转为year_month_day -
month()返回std::chrono::month类型,不能直接与整数 1 比较;要用std::chrono::January或std::chrono::month{1} - 该方式线程安全、无副作用、语义清晰,但 C++20 编译器支持是前提;MSVC 19.30+、GCC 11+、Clang 14+ 支持完整特性
容易忽略的边界:闰秒、时区与本地时间偏差
“该年的第一天”是日历概念,不是时间轴上的绝对点。所以所有判断都应基于本地日历或明确指定时区的日期,而非 UTC 时间戳本身。
例如:某服务器在东京(UTC+9),2024-01-01T00:00:00+09:00 是年初;但同一时刻的 UTC 时间是 2023-12-31T15:00:00Z,若错误地用 UTC 的 tm 结构去判,会得出“不是年初”的错结论。
- 用
std::localtime时,结果依赖tzset()和环境变量TZ,多线程下需注意静态缓冲区竞争 - 用
std::chrono::zoned_time(C++20)可显式绑定时区,避免歧义 - 闰秒不影响“年第一天”判断——因为闰秒只出现在 6 月或 12 月底,跟 1 月 1 日无关
真正要盯住的,是输入源的时间基准:它标的是本地时间?UTC?还是某个固定时区?没理清这点,代码逻辑再正确也会出错。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











