20. 日期与时间
C 语言提供了 time.h 头文件来处理日期和时间。这个库包含了处理时间的函数和类型定义。
1. 重要的时间类型
1.1 日历时间 time_t
time_t 是一个用于存储时间的基本类型,通常是一个长整型,表示从 1970 年 1 月 1 日 00:00:00 UTC(称为 UNIX 纪元)开始经过的秒数。
1.2 分解时间 struct tm
struct tm 是一个用于保存日期和时间信息的结构体,包含以下成员:
struct 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,0 表示一月)
int tm_year; // 年 (从 1900 年开始的年数)
int tm_wday; // 星期几 (0-6,0 表示星期日)
int tm_yday; // 一年中的第几天 (0-365)
int tm_isdst; // 夏令时标志
};2
3
4
5
6
7
8
9
10
11
2. 常用时间函数
2.1 获取当前时间
time_t time(time_t* timer);这个函数返回当前的系统时间。
2.2 时间转换函数
struct tm* localtime(const time_t* timer); // 将 time_t 转换为本地时间
struct tm* gmtime(const time_t* timer); // 将 time_t 转换为 UTC 时间
time_t mktime(struct tm* timeptr); // 将 struct tm 转换为 time_t2
3
4
2.3 格式化时间字符串
[[deprecated]] char* ctime(const time_t* timer); // 将 time_t 转换为字符串
[[deprecated]] char* asctime(const struct tm* timeptr); // 将 struct tm 转换为字符串
size_t strftime(char* str, size_t maxsize, const char* format, const struct tm* timeptr); // 格式化时间字符串2
3
4
2.4 使用示例
#include <stdio.h>
#include <time.h>
int main() {
// 获取当前时间
time_t now;
time(&now);
// 转换为本地时间
struct tm *local = localtime(&now);
// 使用 strftime 格式化输出
char str[80];
strftime(str, sizeof(str), "%Y-%m-%d %H:%M:%S", local);
printf("当前时间:%s\n", str);
// 使用 ctime 直接输出
// printf("ctime 输出:%s", ctime(&now));
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
若本地时间为 2026 年 7 月 26 日 14:30:00,输出为:
当前时间:2026-07-26 14:30:00
动手试试
strftime 交互页面可以逐个编辑 struct tm 的成员,查看每个转换说明符读取哪些成员、输出什么。页面依据 C23 的 "C" 区域设置,实现定义的部分按 x86-64 Linux(glibc,TZ=UTC)处理;所读成员超出正常范围时会直接给出提示。%Ob、%OB 从 C23 开始才可用;页面上的 tm_gmtoff 与 tm_zone 是 glibc 和 POSIX 的扩展成员,不属于 ISO C。
2.5 可重入 (Reentrant) 的时间函数
ctime()、asctime()、localtime()、gmtime() 返回的指针通常指向静态存储区,因此并发场景下需要额外同步。ctime() 与 asctime() 在 C23 中已被弃用,推荐统一使用 strftime() 做格式化输出。
localtime_r() 与 gmtime_r() 原属 POSIX,C23 起纳入 ISO C。它们把结果写入调用方提供的 struct tm 对象:
struct tm *localtime_r(const time_t *timer, struct tm *buf); // C23 起
struct tm *gmtime_r(const time_t *timer, struct tm *buf); // C23 起2
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now = time(NULL);
struct tm *tm_ptr = localtime(&now);
char buf[64];
if (tm_ptr == NULL) {
return 1;
}
if (strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tm_ptr) == 0) {
return 1;
}
puts(buf);
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
在同一假设下,输出为:
2026-07-26 14:30:00
3. 计时函数
3.1 CPU 时间 clock()
clock() 返回进程消耗的处理器时间刻度值,单位不是秒,而是“时钟滴答”。要换算为秒,需要除以 CLOCKS_PER_SEC。
clock_t start = clock();
/* ... 计算任务 ... */
clock_t end = clock();
double cpu_seconds = (double)(end - start) / CLOCKS_PER_SEC;2
3
4
它适合粗粒度性能估计,不适合做高精度墙钟时间测量。
3.2 宏 CLOCKS_PER_SEC
这个宏给出 clock() 结果转秒的换算因子。它是编译期常量,不同实现可能取不同值,因此不应手写假定值。
4. 高精度时间测量函数
C 语言中提供了一种结构体 struct timespec,用于表示高精度(精确到纳秒的)时间。
struct timespec {
time_t tv_sec; // 秒
long tv_nsec; // 纳秒
};2
3
4
4.1 获取高精度当前时间
ISO C 标准接口是 timespec_get(C11 起):
int timespec_get(struct timespec *ts, int base);当 base == TIME_UTC 且函数返回 TIME_UTC 时,ts 中写入当前 UTC 时间。
struct timespec ts;
if (timespec_get(&ts, TIME_UTC) == TIME_UTC) {
/* ts.tv_sec 与 ts.tv_nsec 可用 */
}2
3
4
4.2 获取时钟分辨率
C23 新增了 timespec_getres,用于查询指定时间基准的分辨率:
int timespec_getres(struct timespec *res, int base);C23 之前,可以改用平台提供的接口。
5. 时间计算
5.1 计算时间差
double difftime(time_t time2, time_t time1); // 返回两个时间之间的秒数差difftime(time2, time1) 计算 time2 - time1,结果以秒为单位,类型为 double。当 time_t 不是简单整数类型时,difftime 比直接相减更可靠。
5.2 时间操作示例
#include <stdio.h>
#include <time.h>
int main() {
time_t start, end;
time(&start);
// 执行一些操作
time(&end);
double diff = difftime(end, start);
printf("耗时:%f 秒\n", diff);
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
两次 time 调用之间几乎没有其他操作,通常得到相同的值,此时输出为:
耗时:0.000000 秒
6. 习题
实现 time_duration 结构体,并实现以下操作:
struct time_duration duration(time_t, time_t);time_t post(time_t, const struct time_duration *);time_t pre(time_t, const struct time_duration *);time_duration之间的加法和减法;time_duration与int之间的乘法;
实现 struct tm strptime(const char* str, const char* format) 函数,用于从一个字符串当中按照特定格式读取时间信息。
实现 int days_between(int year1, int month1, int day1, int year2, int month2, int day2) 函数,返回两个日期之间的天数。
使用公历规则计算十亿秒后的时刻:
#include <stdbool.h>
#include <stdint.h>
typedef struct {
int64_t year;
unsigned month;
unsigned day;
unsigned hour;
unsigned minute;
unsigned second;
} CivilTime;
bool add_gigasecond(CivilTime start, CivilTime *out);2
3
4
5
6
7
8
9
10
11
12
13
要求:
- 输入采用无时区的公历时刻,年份不小于 1;秒的取值范围为 0 到 59,不考虑闰秒。
- 使用公历闰年规则,不调用依赖本地时区的转换函数。
- 结果必须恰好比输入晚
1000000000秒。 - 日期无效、
out为NULL或年份计算溢出时返回false,并保持输出对象不变。