C/C++时间操作(ctime、chrono)

1. 核心时间概念

(1)时间戳:从「Unix 纪元(1970-01-01 00:00:00 UTC)」到当前的秒数 / 毫秒数,是绝对时间。

(2)本地时间 / UTC    

- 本地时间:操作系统时区对应的时间;

- UTC:世界协调时间(无时区偏移)

(3)时钟类型    

- system_clock:系统墙钟(可被修改,如手动调时间),可转时间戳
- steady_clock:稳定时钟(不可修改),适合计算时间差
- high_resolution_clock:高精度时钟(通常是前两者的别名)

2. C 风格时间库:<ctime>

<ctime> 库基于 C 语言的 <time.h>,主要用于将时间点转换为人类可读的日期和时间字符串。

<ctime> 库的核心在于两种时间表示形式:

time_t: 表示自 Unix 纪元(1970 年 1 月 1 日 00:00:00 UTC) 以来经过的秒数。

struct tm: 一个包含日期和时间组件(年、月、日、时、分、秒等)的结构体。

2.1. 时间操作

2.1.1. clock()

返回程序启动到调用此函数时所消耗的处理器时间(CPU时间),单位是时钟滴答数 (clock ticks)。主要用于测量程序代码段的执行耗时,而不是实际的挂钟时间。

clock_t clock (void);

clock_t:能够表示时钟滴答数的基本算术类型的别名。
时钟滴答是恒定但系统特定长度的时间单位。
通过宏 CLOCKS_PER_SEC 转换为秒:CPU时间(秒) = clock() / (double)CLOCKS_PER_SEC
#include <iostream>
#include <ctime>
#include <unistd.h>

int main() 
{
    // 记录起始 CPU 时间
    clock_t start = clock();

    // 耗时操作
    int sum = 0;
    for (long long i = 0; i < 1e8; ++i) {
        sum += i;
    }
    sleep(2); // 此段时间不占用 CPU,不会被统计

    // 记录结束 CPU 时间
    clock_t end = clock();

    // 转换为秒
    double cpu_time = (double)(end - start) / CLOCKS_PER_SEC;
    std::cout << "CPU 耗时:" << cpu_time << " 秒" << std::endl;
    return 0;
}

关键特性    

- sleep()/usleep() 期间进程挂起,不占用 CPU,不计入返回值;
- 多线程程序中,返回所有线程 CPU 时间的总和;
- CLOCKS_PER_SEC 是标准宏(通常为 1000000,即 1 微秒 / 滴答)

注意:用 <ctime> 时,加 std:: 是符合 C++ 标准的写法,不加也能运行(编译器兼容),但推荐加以避免命名冲突。

2.1.2. time()    

获取当前的日历时间 (time_t)。这是获取当前时间戳(自纪元以来的秒数)的主要方法。传入 NULL 或指向 time_t 变量的指针。

time_t time (time_t* timer);

参数:
- 传入 NULL:仅返回时间戳,不修改外部变量(最常用);
- 传入 time_t* 指针:将时间戳同时写入该指针指向的变量,返回值与该变量值一致

返回值:
成功:返回自 Unix 纪元(1970-01-01 00:00:00 UTC) 以来的秒数(time_t 类型)
失败:返回 (time_t)-1。

多数系统中 time_t 是 64 位整数(可表示到 292 亿年后),
32 位系统中 time_t 是 32 位整数,会在 2038 年溢出(2038 年问题),
需编译为 64 位程序(-m64)。
#include <iostream>
#include <ctime>
#include <unistd.h>

int main()
{
    time_t begin = time(NULL);

    int sum = 0;
    for (long long i = 0; i < 1e8; ++i) {
        sum += i;
    }
    sleep(2);
    time_t end = time(NULL);
    std::cout << "程序开始时间:" << begin << std::endl;
    std::cout << "程序结束时间:" << end << std::endl;
    time_t ret = time(&begin);
    std::cout << "ret:" << ret << std::endl;
    return 0;
}

核心特性:

- 统计的是系统真实流逝时间(墙上时钟),包含 sleep()/ 进程挂起时间;
- 精度为秒级,无法获取毫秒 / 微秒级时间;
- 受系统时区 / 时间设置影响(但时间戳本身是 UTC 基准)

2.1.3. difftime()

 C/C++ 标准库中专门用于计算两个 time_t 时间戳差值的函数,核心特点是返回高精度浮点数,避免直接减法的潜在问题。

double difftime (time_t end, time_t beginning);

- end:结束时间戳
- beginning:起始时间戳

返回值
以秒为单位的时间差(double 类型),支持小数(如 2.5 秒),
本质等价于 (double)(time_end - time_beg)
#include <iostream>
#include <ctime>

int main()
{
    time_t begin = time(NULL);

    int sum = 0;
    for (long long i = 0; i < 1e8; ++i) {
        sum += i;
    }
    sleep(2);
    
    time_t end = time(NULL);
    std::cout << "程序运行时间:" << end - begin << std::endl;
    std::cout << "程序运行时间时间:" << difftime(end, begin) << std::endl;
    return 0;
}

特性:

精度限制:difftime() 的精度由 time_t 决定 —— 若 time_t 仅精确到秒(绝大多数系统),返回值的小数部分始终为 0;只有当 time_t 支持毫秒 / 微秒级时,小数部分才有效。

溢出安全:若 time_t 是 32 位有符号整数(范围:1901~2038),两个时间戳差值过大时直接减法会溢出,而 difftime() 先将 time_t 转为 double(范围远大于 32 位整数),避免溢出:

2.1.4. mktime()

用于将本地时间的 tm 结构体转换为 time_t 时间戳。通过 time_t 还可以补全 tm 的星期 / 年内天数。

time_t mktime (struct tm * timeptr);

timeptr:指向 tm 结构体的指针,该结构体需填充本地时间的年/月/日/时/分/秒等字段

成功:返回对应时间的 time_t 时间戳(自 1970-01-01 UTC 的秒数);
失败:返回 (time_t)-1
struct tm
{
    int tm_sec;   /* Seconds (0-60) */
    int tm_min;   /* Minutes (0-59) */
    int tm_hour;  /* Hours (0-23) */
    int tm_mday;  /* Day of the month (1-31) */
    int tm_mon;   /* Month (0-11) */
    int tm_year;  /* Year - 1900 */
    int tm_wday;  /* Day of the week (0-6, Sunday = 0) */未赋值时 mktime() 自动计算
    int tm_yday;  /* Day in the year (0-365, 1 Jan = 0) */未赋值时 mktime() 自动计算
    int tm_isdst; /* Daylight saving time */
};

tm_isdst 是用于标记夏令时 状态的字段

1	明确表示当前时间处于夏令时时段(夏令时生效,时钟调快 1 小时);
0	明确表示当前时间不处于夏令时时段(标准时间);
-1	不指定夏令时状态,让系统自动检测 / 计算当前时间是否适用夏令时(默认推荐值);

当给 tm_yday(年内天数)、tm_wday(星期数)赋值错误时,mktime() 会直接忽略这些错误值,并根据 tm_year/tm_mon/tm_mday 等核心字段重新计算、覆盖为正确值。

int main()
{
    struct tm t = {};
    t.tm_year = 125;
    t.tm_mon = 11; // 0~11
    t.tm_mday = 6;
    t.tm_hour = 14;
    t.tm_min = 19;
    t.tm_sec = 30;
    t.tm_yday = 1;
    t.tm_wday = 1;

    time_t utctime = mktime(&t);
    
    std::cout << "2025-12-6 14::19::30 的时间戳:" << utctime << std::endl;
    std::cout << "2025-12-06 是星期:" << t.tm_wday << "(0=周日,6=周六)" << std::endl;
    std::cout << "2025-12-06 是年内第:" << t.tm_yday << " 天(0=1月1日)" << std::endl;
    return 0;
}

特性:

1. 自动规范化:修正 tm 中的非法值(如月份 13→次年 1 月、日期 32→次月 2 日);
2. 填充补全:若 tm_wday(星期)、tm_yday(年内天数)未赋值,会自动计算并填充;
3. 本地时区敏感:转换结果受系统本地时区影响(如北京时间 UTC+8)

2.2. 时间格式转换函数

2.2.1. localtime()

time_t 值转换为表示 本地时间 的 struct tm 结构体。

struct tm * localtime (const time_t * timer);

参数 timep:指向 time_t 时间戳的指针(通常由 time() 获取);
返回值:指向静态 tm 结构体的指针(成功),或 NULL(失败,如时间戳超出范围)。
int main()
{
    time_t tt = time(NULL);
    struct tm *local_tm = localtime(&tt);
    std::cout << "当前本地时间:"
              << (local_tm->tm_year + 1900) << "-"
              << (local_tm->tm_mon + 1) << "-"
              << local_tm->tm_mday << " "
              << local_tm->tm_hour << ":"
              << local_tm->tm_min << ":"
              << local_tm->tm_sec << std::endl;
    return 0;
}

注意事项:

1. 静态缓冲区的覆盖问题(核心风险)

localtime 返回的 tm 结构体是全局静态内存,后续调用 localtime/gmtime/ctime 等函数会覆盖之前的结果。例如:

std::time_t t1 = ...;
std::time_t t2 = ...;
std::tm* tm1 = std::localtime(&t1); // tm1指向静态缓冲区
std::tm* tm2 = std::localtime(&t2); // 静态缓冲区被覆盖,tm1和tm2的内容都会变成t2的时间

2. 线程不安全

由于使用静态缓冲区,多线程同时调用 localtime 会导致数据竞争,在Linux/macOS中可使用localtime_r(POSIX 标准)替代。

3. 时间范围限制

32 位 time_t:仅支持 1970-01-01 ~ 2038-01-19;
64 位 time_t:支持到 3000-12-31(现代系统默认是 64 位)。
若时间戳超出范围,localtime 返回 NULL。

2.2.2. gmtime()

将 time_t 时间戳转换为 UTC 时区 tm 结构体.

struct tm * gmtime (const time_t * timer);

返回值:指向静态 tm 结构体的指针(成功),或 NULL(失败,如时间戳超出范围)。
#define UTC (0)
#define CCT (+8)

int main() {
    time_t now = time(nullptr);
    
    struct tm* ptm = gmtime(&now);
    
    printf ("Reykjavik (Iceland) : %2d:%02d\n", (ptm->tm_hour+UTC)%24, ptm->tm_min);
  printf ("Beijing (China) :     %2d:%02d\n", (ptm->tm_hour+CCT)%24, ptm->tm_min);
    return 0;
}

同样存在静态缓冲区的覆盖、线程不安去等问题,Linux/macOS(POSIX)中,可使用gmtime_r。

2.2.3. asctime()

把 tm结构体(时间结构体)转换成可读的字符串,固定格式为 Www Mmm dd hh:mm:ss yyyy\n

如Wed Jun 30 21:49:08 1993。

char* asctime (const struct tm * timeptr);

返回值:指向静态字符串的指针(格式固定为 26 字符,含换行符 \n 和结束符 \0);
失败返回:NULL(极少发生,仅 tm 结构体字段非法且无法解析时)。
int main()
{
    time_t now = time(NULL);
    struct tm * utc_tm = gmtime(&now);
    char* asctm = asctime(utc_tm);
    std::cout << "UTC 时间字符串:" << asctm;
    return 0;
}

同样存在静态缓冲区覆盖和线程不安全问题,

2.2.4. ctime()

 直接将 time_t 时间戳转换为本地时区固定格式字符串,ctime() 始终输出本地时区时间。

char* ctime (const time_t * timer);

返回值:指向静态字符串的指针(格式与 asctime() 完全一致,26 字符,含 \n 和 \0);
底层逻辑:ctime(timep) = asctime(localtime(timep))(先转本地时区 tm 结构体,再转字符串)。
int main()
{
    time_t now = time(NULL);
    char* tm_str = ctime(&now);
    std::cout << tm_str;
    return 0;
}

2.2.5. strftime()

高度灵活的时间格式化函数。使用指定的格式字符串将 struct tm 中的时间信息转换为自定义格式的字符串。

size_t strftime (char* ptr, size_t maxsize, const char* format,
const struct tm* timeptr );

str	输出缓冲区(自定义数组),存储格式化后的字符串
maxsize	缓冲区最大长度(避免溢出)
format	格式控制字符串(含普通字符 + 格式占位符,如 %Y-%m-%d %H:%M:%S)
timeptr	指向 tm 结构体的指针(可由 localtime()/gmtime() 生成)
%Y	4 位年份
%y	2 位年份(00~99)	25
%m	月份(01~12)	
%d	日期(01~31)	
%H	小时(24 小时制,00~23)
%I	小时(12 小时制,01~12)
%M	分钟(00~59)
%S	秒(00~59)
%a	星期缩写(本地语言)	Sat / 周六
%A	星期全称(本地语言)	Saturday / 星期六
%b	月份缩写(本地语言)	Dec / 12 月
%B	月份全称(本地语言)	December / 十二月
%p	上午 / 下午(12 小时制)	PM / 下午
%z	时区偏移(如 +0800)	+0800
%Z	时区名称(如 CST)	CST
int main()
{
    time_t tt = time(NULL);
    struct tm *lt = localtime(&tt);
    char buf[64];
    strftime(buf, sizeof(buf) - 1, "%Y-%m-%d %H:%M:%S %p", lt); 
    std::cout << buf << std::endl;
    return 0;
}

3. C++chrono库

<chrono> 是 C++11 引入的现代时间处理库,核心解决了传统 C 风格时间函数(time()/gmtime() 等)的痛点:类型不安全、精度固定、时区处理繁琐、无面向对象封装。C++14 进一步补充了小数秒、字面量等特性。C++20 引入了日历和时区支持,极大地增强了功能。

<chrono> 库提供了三个核心概念:时长 (Duration)、时钟 (Clock)时间点 (Time Point) 。

3.1. 时长(duration)

std::chrono::duration 表示一段时间,由一个计数 (count) 和一个时间单位 (tick period) 组成。

模板原型:template <class Rep, class Period = ratio<1>> class duration;
Rep:存储时间数值的类型(如 int/long/double);
Period:时间单位(ratio<1>= 秒,ratio<1,1000>= 毫秒,ratio<1,1000000>= 微秒)。
chrono::nanoseconds	纳秒	duration<long long, ratio<1,1e9>>
chrono::microseconds	微秒	duration<long long, ratio<1,1e6>>
chrono::milliseconds	毫秒	duration<long long, ratio<1,1000>>
chrono::seconds	秒	duration<long long>
chrono::minutes	分钟	duration<long long, ratio<60>>
chrono::hours	小时	duration<long long, ratio<3600>>

时长相加的类型推导规则:std::chrono::duration(时长)的加法运算遵循「低精度向高精度转换」原则。

<chrono> 库对「时长 ÷ 数值」的重载规则和单位缩放逻辑:

数值层面:时长的计数值(count())除以该数;
单位层面:时长的单位(Period)乘以该数(即缩放单位);

#include <iostream>
#include <chrono>

int main()
{
    std::chrono::seconds s(5);
    std::chrono::milliseconds m(1500);
    
    auto msm = s + m;
    auto ssm = msm / 1000;
    std::cout << "5秒 + 1500毫秒=" << msm.count() << "毫秒" << std::endl;
    std::cout << "转换为秒:" << ssm.count() << std::endl;
    
    std::chrono::seconds s_cast = std::chrono::duration_cast<std::chrono::seconds>(msm);
    std::cout << "s_cast:" << s_cast.count() << std::endl;
    return 0;
}

3.2. 时间点(Time Point)

时间点是 C++11 <chrono> 库中表示「某个具体时刻」的类模板,核心是绑定时钟 + 记录从时钟纪元开始的时长

template <class Clock, class Duration = typename Clock::duration>
class time_point;
Clock:时间参考系(如 system_clock/steady_clock);
Duration:从时钟「纪元」到该时间点的时长(如秒 / 毫秒)。

system_clock:纪元为 1970-01-01 UTC(与 time_t 兼容);
steady_clock:无固定纪元,仅相对值有效(计时专用)。

常用操作

获取当前时间点	auto tp = std::chrono::system_clock::now();
转 time_t(秒级)	time_t t = std::chrono::system_clock::to_time_t(tp);
从 time_t 转回	auto tp = std::chrono::system_clock::from_time_t(t);
时间点运算	auto tp2 = tp1 + std::chrono::seconds(10);(10 秒后)
计算时间差	auto dur = tp2 - tp1;(结果为 duration 类型)

system_clock::now() 含毫秒 / 微秒精度,转 time_t 会丢失小数秒;

3.3. 时钟(Clock)

<chrono> 提供 3 种核心时钟,适配不同场景:

system_clock:对应系统时间(可手动修改),纪元为 1970-01-01 UTC(Unix 时间戳
steady_clock:单调递增时钟(不受系统时间修改影响),精度高(纳秒级)
high_resolution_clock:系统最高精度时钟(通常是 steady_clock 或 system_clock 的别名)

system_clock

system_clock::now():获取当前系统时间的 time_point(时间点对象)
system_clock::to_time_t(tp):将 system_clock 的时间点 tp 转换为传统 time_t 时间戳(秒级)
system_clock::from_time_t(t):将 time_t 时间戳 t 转换为 system_clock 的时间点(反向兼容)

steady_clock

steady_clock::now():获取单调递增时钟当前时间点
特性	
单调性	时间永不回退,now() 返回值只增不减(核心优势)
纪元	无固定纪元(仅相对时间差有效,无法转换为日历时间)
精度	系统最高精度(通常纳秒级)
核心用途	性能测试、超时判断、帧率统计等 “相对计时” 场景
不可转换 time_t	无 to_time_t/from_time_t 接口(无绝对时间意义)
#include <iostream>
#include <chrono>
#include <thread>
#include <ctime>

int main() 
{
    auto begin = std::chrono::steady_clock::now();
    time_t t1 = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
    std::cout << t1 << std::endl;

    std::this_thread::sleep_for(std::chrono::milliseconds(150));
    int sum = 0;
    for (long long i = 0; i < 1e8; ++i) sum += i;

    auto end = std::chrono::steady_clock::now();
    time_t t2 = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
    auto duration_ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin);
    
    std::cout << t2 << std::endl;
    std::cout << duration_ms.count() << " 毫秒" << std::endl;
    return 0;
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值