最稳妥的方式是使用 std::chrono::year_month:先用 system_clock::now() 获取时间点,再 floor 到 days 精度,构造 year_month 对象,直接提取 year 和 month 成员,类型安全、无时区歧义、跨平台一致,避免 localtime 线程不安全及 tm 成员偏移错误。

用 std::chrono + std::time_t 获取年月整数(C++11 及以上)
最稳妥的方式是走标准库时间流:先获取当前时间点,转成 std::time_t,再用 std::localtime 或 std::gmtime 解析。注意 std::localtime 返回的是非线程安全的静态结构体指针,多线程下要改用 std::localtime_r(POSIX)或 std::localtime_s(Windows)。
示例代码:
#include <chrono>
#include <ctime>
#include <iostream>
int main() {
auto now = std::chrono::system_clock::now();
std::time_t t = std::chrono::system_clock::to_time_t(now);
std::tm tm_buf {};
#if defined(_MSC_VER)
std::localtime_s(&tm_buf, &t);
#else
std::localtime_r(&t, &tm_buf);
#endif
int year = tm_buf.tm_year + 1900;
int month = tm_buf.tm_mon + 1; // tm_mon 是 0~11
std::cout << year << "-" << month << "\n";
}
-
tm_year是自 1900 年起的偏移量,必须加 1900 -
tm_mon是 0 起始(0 表示 1 月),必须加 1 - Windows 下用
std::localtime_s,Linux/macOS 用std::localtime_r,否则多线程可能读到脏数据 - 如果只需要 UTC 时间,用
std::gmtime_r/std::gmtime_s替代
用 std::format(C++20)直接格式化并提取数字
C++20 引入了 std::format,可以避免手动处理 tm 结构体,但要注意它不直接返回整数——得靠字符串解析或搭配 std::chrono::year_month 使用。
更推荐的方式是结合 std::chrono::floor<std::chrono::days> 和 std::chrono::year_month:
立即学习“C++免费学习笔记(深入)”;
#include <chrono>
#include <iostream>
int main() {
using namespace std::chrono;
auto now = system_clock::now();
auto dp = floor<days>(now);
auto ym = year_month{dp};
int year = int(ym.year());
int month = unsigned(ym.month());
std::cout << year << "-" << month << "\n";
}
- 依赖
<chrono>,无需<ctime>,类型安全,无时区歧义 -
year_month构造隐式使用本地日历,但底层仍是 UTC 时间点 + 本地偏移计算,结果等价于localtime - 若需强制 UTC,应先用
zoned_time{"UTC", now}转换后再取year_month - VS 2022 17.5+、GCC 13+、Clang 16+ 才完整支持
year_month构造
常见错误:直接用 time(nullptr) + localtime 而不考虑线程安全
很多旧代码写成 std::localtime(&t),在多线程环境里会崩溃或返回错误年月——因为返回值指向内部静态缓冲区,被多个线程反复覆盖。
- 编译期不会报错,运行时偶发错(比如某次输出是 2023-13 或 0-0)
- 即使单线程,如果中间调用了其他也用
localtime的库函数(如strptime),也可能被污染 - MacOS 不支持
localtime_r,需用localtime_s(但 macOS 默认没开 C11 支持,得加-std=c11或改用localtime+ 锁) - Android NDK r21+ 才提供
localtime_r;老版本只能用__android_log_print配合临时tm实例手动复制
为什么不用 strftime 提取数字?
有人想用 strftime 写成 "%Y-%m" 再 sscanf,看似简洁,但实际多此一举且引入额外风险:
- 字符串转换增加开销,对高频调用场景(如日志打点)不友好
-
sscanf失败时不设默认值,容易让year/month保持未初始化垃圾值 - 格式字符串受 locale 影响:
setlocale(LC_TIME, "zh_CN")后某些实现仍能正常输出数字,但不保证 - 不如直接访问
tm成员或year_month成员来得直白可靠
真正难的不是“怎么拿到年月”,而是确保线程安全、跨平台一致、以及别把 tm_mon 当成 1~12 用。这两点漏掉一个,上线后都得查半夜日志。


















