C++ 标准库 ctime
下面对 C++ 标准库中 <ctime>
头文件提供的日期时间类型与函数做一次系统、深入的梳理,包括类型定义、常用函数、格式化输入/输出、安全性问题、以及实践建议。
一、概述
<ctime>
(等同于 C 语言的<time.h>
)定义了表示与操作时间的基本类型和函数。- 主要用途包括获取当前时间、实现时间差计算、在日历(年/月/日、时分秒)与原始时间戳间转换,以及将时间格式化为字符串或从字符串解析时间等。
- 底层依赖于平台 C 运行时,通常以秒为基本单位;注意各函数的线程安全性与时区影响。
二、主要类型
类型 | 描述 |
---|---|
std::time_t | 以实现定义的整数或浮点类型保存的“原始”时间戳,通常为自 1970‑01‑01 00:00:00 UTC 起的秒数。 |
std::clock_t | 用于 std::clock() 返回的 CPU 时间计数;与 CLOCKS_PER_SEC 配合使用,单位通常是时钟滴答。 |
std::tm | 结构体,按日历分解时间,包含年月日、时分秒、周、年中日数、夏令时标志等字段。 |
std::size_t | 用于 strftime 等函数的计数参数。 |
struct std::tm {
int tm_sec; // 秒 [0,60](允许闰秒)
int tm_min; // 分 [0,59]
int tm_hour; // 时 [0,23]
int tm_mday; // 一月中的日 [1,31]
int tm_mon; // 月 [0,11]
int tm_year; // 自 1900 年起的年数
int tm_wday; // 一周中的日 [0,6] 0=星期日
int tm_yday; // 年中日 [0,365]
int tm_isdst; // 夏令时标志 >0=生效,0=不生效,<0=未知
};
三、获取当前时间
1. std::time
std::time_t now = std::time(nullptr);
- 功能:返回当前日历时间(
time_t
),或将其写入传入指针并返回。 - 复杂度:常数时间。
- 注意:受系统时钟影响,可通过
std::gmtime
/std::localtime
转换为日历时间。
2. std::clock
std::clock_t c = std::clock();
double cpu_seconds = double(c) / CLOCKS_PER_SEC;
- 功能:返回程序执行以来所消耗的处理器时钟滴答数。
- 宏:
CLOCKS_PER_SEC
定义每秒的时钟滴答数,常见值是 1e6 或 1e3。 - 用途:用于测量 CPU 时间(非实时),注意在长时间运行下可能溢出。
四、时间戳与日历时间相互转换
1. std::gmtime
/ std::localtime
std::tm* utc = std::gmtime(&now); // 转换为 UTC 日历时间
std::tm* local = std::localtime(&now); // 转换为本地时区日历时间
- 返回:指向静态
std::tm
对象的指针(非线程安全)。 - 线程安全版本(C11/C++20 或 POSIX):
std::gmtime_s(&tm, &now)
/std::localtime_s(&tm, &now)
(Windows)std::gmtime_r(&now, &tm)
/std::localtime_r(&now, &tm)
(POSIX)
2. std::mktime
std::tm tm = *local;
std::time_t t2 = std::mktime(&tm);
- 功能:将本地时区的
std::tm
转换回time_t
,并标准化各字段(如归一化秒/分钟越界、设置夏令时标志)。 - 注意:
tm_isdst
可设为-1
让实现自行判断夏令时;该函数会修改传入的tm
结构。
五、字符串格式化与解析
1. std::asctime
/ std::ctime
char* s1 = std::asctime(&tm); // "Wed Jun 30 21:49:08 1993\n\0"
char* s2 = std::ctime(&now); // 等价于 asctime(gmtime/localtime)
- 返回:静态缓冲区指针,非线程安全。
- 线程安全版本:
asctime_s
/ctime_s
(Windows),或手动用std::strftime
。
2. std::strftime
char buf[100];
std::size_t len = std::strftime(
buf, sizeof(buf),
"%Y-%m-%d %H:%M:%S", // 格式化模板
&tm
);
// buf = "2025-07-12 14:30:59"
- 功能:根据
std::tm
及格式字符串,生成格式化日期时间文本。 - 格式说明符(常用):
%Y
年(四位)%m
月(01–12)%d
日(01–31)%H
时(00–23)%M
分(00–59)%S
秒(00–60,允许闰秒)%z
时区偏移(如+0800
)%Z
时区名称%a
星期缩写(如Mon
)%A
星期全称
- 返回:写入字符数,不含终止
\0
;若缓冲区不足,返回 0。
3. 解析字符串到 std::tm
C++17 标准尚未直接提供解析函数,需借助 C 库或自定义:
std::istringstream iss("2025-07-12 14:30:59");
std::tm tm = {};
iss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S");
if (iss.fail()) { /* 解析失败 */ }
- 需包含
<iomanip>
,std::get_time
从流中按指定格式填充std::tm
。
六、时间差计算
1. std::difftime
double seconds = std::difftime(t2, t1);
- 功能:计算两个
time_t
之间的差值(t2 - t1
),以秒为单位返回double
。 - 优势:避免直接做整数减法导致的溢出或类型问题。
七、实践建议
- 线程安全
- 避免使用
std::gmtime
、std::localtime
、std::asctime
、std::ctime
,改用带_s
(Windows)或_r
(POSIX)的安全版本,或直接用std::strftime
/std::get_time
。
- 避免使用
- 时区与夏令时
- 对跨时区应用,要统一使用 UTC(
gmtime
)存储与计算,展示时才转换为本地时区。 - 夏令时标志由
tm_isdst
控制,通常设为-1
让库函数自动计算。
- 对跨时区应用,要统一使用 UTC(
- 高精度计时
std::clock
仅度量 CPU 时间,分辨率和溢出可能不足。对高精度或实时测量,应使用<chrono>
(如steady_clock
,high_resolution_clock
)。
- 避免缓冲区溢出
strftime
时确保提供足够大的缓冲区,并检查返回值非零。
- 解析格式灵活性
std::get_time
对流输入格式严格;对复杂输入可考虑正则或第三方库(如 Howard Hinnant 的 date 库)。
通过以上对 <ctime>
中类型、转换函数、字符串格式化/解析及注意事项的全面梳理,相信能帮助你在日志记录、调度系统、时间戳管理等场景中安全、正确地使用标准库提供的时间功能。祝编码顺利!