最可靠方法是用 tm_mon / 3 + 1 计算季度(tm_mon 范围 0–11),年份为 tm_year + 1900,并封装为内联函数统一处理,避免整除错误和跨年漏判。

用 std::chrono 提取年份和季度最可靠
直接比较年份和季度数值,比字符串拼接或时间戳除法更清晰、无时区歧义。C++20 的 std::chrono 提供了 year_month_day,但季度需手动计算;C++11/14/17 也可用 std::localtime 配合 tm_year 和 tm_mon 推导。
关键逻辑:季度 = (month - 1) / 3 + 1(month 从 1 开始),年份 = tm_year + 1900(tm_year 是距 1900 年的偏移)。
实操建议:
- 若已知是
std::time_t或std::chrono::system_clock::time_point,先转为std::tm(用std::gmtime或std::localtime,注意线程安全) - 避免直接对
time_t做算术除法判断季度——闰秒、DST 切换会导致结果漂移 - C++20 可用
std::chrono::year_month_day{sys_days}+.month()+.year(),更类型安全
tm_mon 的取值范围容易导致季度算错
tm_mon 是 0–11(不是 1–12),直接套用 (tm_mon + 1) / 3 会因整除截断出错。例如 3 月(tm_mon == 2)→ (2 + 1) / 3 == 1 正确;但 4 月(tm_mon == 3)→ (3 + 1) / 3 == 1 错误(应为 Q2)。
立即学习“C++免费学习笔记(深入)”;
正确写法必须是:(tm_mon / 3) + 1 —— 因为 0–2 → Q1,3–5 → Q2,6–8 → Q3,9–11 → Q4。
常见错误现象:
- 所有 1 月、2 月、3 月被归为 Q1,但 4 月也被判成 Q1
- 传入 2023-04-01 和 2023-06-30 返回 false,实际应为 true
- 使用
(tm_mon + 1) % 3 == 0判断季度起始月,逻辑混乱且不可读
封装成内联函数比每次手算更安全
把年份+季度提取逻辑收拢,避免重复出错。尤其当需要多次判断(如容器过滤、日志分组)时,函数化能统一处理边界(如负年份、无效日期)。
示例(C++11 兼容):
inline std::pair<int, int> get_year_quarter(const std::tm& t) {
int year = t.tm_year + 1900;
int quarter = t.tm_mon / 3 + 1; // tm_mon: 0–11 → 0/3=0 → Q1
return {year, quarter};
}
<p>bool same_quarter(const std::tm& a, const std::tm& b) {
return get_year_quarter(a) == get_year_quarter(b);
}注意点:
- 不校验
tm_mday等字段有效性——调用方应确保输入是合法日期(如来自mktime归一化后) - 返回
std::pair可直接用==比较,无需手写结构体 - 若需支持 UTC,用
std::gmtime替代std::localtime,并加const修饰指针防止意外修改
跨年场景下别忽略年份比较优先级
季度号相同但年份不同(如 2023-Q4 和 2024-Q1)必须返回 false。有人只比季度、漏比年份,导致 2023-10-01 和 2024-01-15 被误判为同季度。
性能与兼容性影响:
- 年份比较是整数比,成本远低于任何时间转换;务必放在季度前判断,短路可省一次除法
- Windows 上
std::localtime返回静态缓冲区,多线程需用localtime_s;Linux/macOS 可用localtime_r - 如果输入是字符串(如
"2023-03-15"),先用strptime或 C++20std::chrono::parse解析,别用sscanf手动拆——月份易错位
真正容易被忽略的是:日期合法性未校验时,tm_mon 可能为 12 或 -1,此时 / 3 仍得整数但语义失效。生产环境建议在 get_year_quarter 内加 assert 或返回 optional。


















