跳到主要内容C++ 高精度时间库 chrono 详解 | 极客日志C++算法
C++ 高精度时间库 chrono 详解
C++ chrono 库解决了传统 time.h 类型不安全与精度低的问题,通过 Duration、Time Point 和 Clock 三大核心概念实现编译期类型安全的时间管理。文章涵盖 C++11 基础时钟与 C++20 新增日历及专业时钟功能,结合 Duration 转换、时间点运算及实际日期类封装示例,演示了如何在性能测试与复杂日期计算中高效应用该库。
lzdxwyh0 浏览 熟悉 C 语言的开发者通常对 <time.h> 不陌生,它操作简单且跨平台一致,但在现代高性能场景下显得力不从心。类型不安全、精度较低(通常为秒)、扩展依赖编译器宏等问题,使得它在需要微秒级性能测试或复杂时间计算时捉襟见肘。
C++11 引入的 库正是为了解决这些问题而生。它将时间抽象为三个核心概念:时间段 (Duration)、时间点 (Time Point) 和时钟 (Clock),实现了编译期类型安全与精度无关的处理模型。官方标准文档提供了详尽参考:Standard library header 。
设计哲学
在 C++11 之前,我们处理时间基本靠 time_t、clock() 和 struct timespec。这种方式的问题在于类型不安全。函数参数传递 time_t,你无法直观知道单位是秒还是毫秒;做时间运算时,极易因单位混淆导致 bug。
<chrono> 库最伟大的贡献,就是引入了编译期类型安全和精度无关的时间处理模型。它将时间抽象为三个核心概念:
- 时间段 (Duration):由数值和单位组成的强类型。例如
std::chrono::seconds(10) 就是 10 秒,std::chrono::milliseconds(1000) 是 1000 毫秒,它们在类型上就不同,不能直接隐式转换,避免了单位混淆。
- 时间点 (Time Point):某个特定时钟上的一个点,由时钟 (Clock) 和时间段 (Duration) 共同定义。它本质上是从某个固定的纪元 (Epoch) 开始到现在的时间段。例如
std::chrono::system_clock::now() 返回的就是一个时间点。
- 时钟 (Clock):提供了获取当前时间点的入口 (
now()),并定义了时间点的精度和纪元。标准提供了三种时钟(如文档所列),这是新手最容易踩坑的地方,下面我会重点讲。
库中的单位
时长单位(Duration Units)
这些是表示时间间隔的类型,基于 duration 模板定义。
| 单位名称 | 引入标准 | 说明 |
|---|
std::chrono::nanoseconds | C++11 | 纳秒,周期为 std::nano |
std::chrono::microseconds | C++11 | 微秒,周期为 std::micro |
std::chrono::milliseconds | C++11 | 毫秒,周期为 std::milli |
std::chrono::seconds | C++11 | 秒,周期为 std::ratio<1> |
std::chrono::minutes | C++11 | 分钟,周期为 std::ratio<60> |
std::chrono::hours | C++11 | 小时,周期为 std::ratio<3600> |
std::chrono::days | C++20 |
std::chrono::weeks | C++20 | 周,周期为 std::ratio<604800> |
std::chrono::months | C++20 | 月(平均格里高利月),周期为 std::ratio<2629746> |
std::chrono::years | C++20 | 年(平均格里高利年),周期为 std::ratio<31556952> |
时钟单位(Clock Types)
时钟定义了时间的原点(纪元)和刻度,用于产生时间点。
| 时钟名称 | 引入标准 | 说明 |
|---|
std::chrono::system_clock | C++11 | 系统范围实时时钟(挂钟时间),通常基于 Unix 时间戳 |
std::chrono::steady_clock | C++11 | 单调时钟,不受系统时间调整影响,适合测量间隔 |
std::chrono::high_resolution_clock | C++11 | 当前设置下最高精度的时钟,通常是上述之一的别名 |
std::chrono::utc_clock | C++20 | 协调世界时(UTC),处理跳秒 |
std::chrono::tai_clock | C++20 | 国际原子时(TAI),与 UTC 有固定偏移且无跳秒 |
std::chrono::gps_clock | C++20 | 全球定位系统时(GPS),始于 1980-01-06 |
std::chrono::file_clock | C++20 | 文件系统使用的时钟别名,用于获取文件时间戳 |
std::chrono::local_t | C++20 | 伪时钟,代表本地时间,不关联具体时区 |
日历基础单位(Calendar Fundamentals)
C++20 引入,表示格里高利历中的基本日期元素。
| 类型名称 | 引入标准 | 说明 |
|---|
std::chrono::day | C++20 | 表示月份中的某一天(1-31) |
std::chrono::month | C++20 | 表示年份中的某个月(1-12) |
std::chrono::year | C++20 | 表示格里高利历中的年份(如 2024) |
std::chrono::weekday | C++20 | 表示星期几(0 代表周日,1 周一,...6 周六) |
std::chrono::last_spec | C++20 | 标签类,用于表示月份的最后一天或最后一个工作日 |
日历复合单位(Calendar Composites)
由基础类型组合而成,用于表示完整的日期或特定规则。
| 类型名称 | 引入标准 | 说明 |
|---|
std::chrono::year_month | C++20 | 特定的年 + 月组合(如 2024y/May) |
std::chrono::month_day | C++20 | 特定的月 + 日组合(如 May/21d),忽略年份 |
std::chrono::year_month_day | C++20 | 完整的日期(如 2024-05-21) |
std::chrono::year_month_day_last | C++20 | 某月的最后一天(如 2024 年 5 月的最后一天) |
std::chrono::month_day_last | C++20 | 某月的最后一天,忽略年份 |
std::chrono::year_month_weekday | C++20 | 某年第 N 个月的星期 W(如 2024 年 5 月的第 3 个星期二) |
std::chrono::year_month_weekday_last | C++20 | 某个月的最后一个星期 W |
std::chrono::month_weekday | C++20 | 某个月的第 N 个星期 W,忽略年份 |
std::chrono::month_weekday_last | C++20 | 某个月的最后一个星期 W,忽略年份 |
std::chrono::weekday_indexed | C++20 | 表示某个月的第几个星期几(如第 3 个星期二) |
std::chrono::weekday_last | C++20 | 表示某个月的最后一个星期几 |
时刻单位(Time of Day)
| 类型名称 | 引入标准 | 说明 |
|---|
std::chrono::hh_mm_ss | C++20 | 将时长拆分为小时:分钟:秒及亚秒的模板类,支持 12 小时/24 小时格式 |
时区单位(Time Zone)
| 类型名称 | 引入标准 | 说明 |
|---|
std::chrono::time_zone | C++20 | 代表一个 IANA 时区(如"Asia/Shanghai") |
std::chrono::zoned_time | C++20 | 将时区与时间点关联的组合类型 |
std::chrono::tzdb | C++20 | IANA 时区数据库的快照 |
std::chrono::sys_info | C++20 | 特定时间点在某个时区的详细信息(偏移量、缩写、是否 DST) |
std::chrono::local_info | C++20 | 描述本地时间到系统时间转换的低级信息(用于处理歧义或不存在的时间) |
std::chrono::choose | C++20 | 枚举,用于指定在本地时间歧义时如何选择(earliest 或 latest) |
std::chrono::time_zone_link | C++20 | 时区的替代名称(链接) |
std::chrono::nonexistent_local_time | C++20 | 异常,表示本地时间不存在(如夏令时快进时段) |
std::chrono::ambiguous_local_time | C++20 | 异常,表示本地时间有歧义(如夏令时回拨时段) |
字面量单位(Literals)
定义在 std::literals::chrono_literals 内联命名空间中,用于便捷书写。
| 字面量运算符 | 引入标准 | 返回类型/说明 |
|---|
operator""h | C++14 | 返回 std::chrono::duration,表示小时 |
operator""min | C++14 | 返回 std::chrono::duration,表示分钟 |
operator""s | C++14 | 返回 std::chrono::duration,表示秒 |
operator""ms | C++14 | 返回 std::chrono::duration,表示毫秒 |
operator""us | C++14 | 返回 std::chrono::duration,表示微秒 |
operator""ns | C++14 | 返回 std::chrono::duration,表示纳秒 |
operator""y | C++20 | 返回 std::chrono::year,表示年份 |
模板类
std::chrono::duration
这是最核心的类模板,它将一个数值(tick 数)和一个时间单位(tick 周期)绑定在一起,实现了类型安全的时间长度表示。
template<class Rep,class Period = std::ratio<1>> class duration;
**Rep**:存储 tick 计数的算术类型,如 long long、int 或 double。
**Period**:一个 std::ratio 类型,编译期指定每个 tick 代表的秒数。例如,std::ratio<1> 为 1 秒,std::milli 为 1/1000 秒。
| 接口类别 | 表达式/代码示例 | 作用与说明 |
|---|
| 构造函数 | duration<Rep, Period> d(5); | 用 tick 计数值构造时长。通常通过预定义类型或字面量创建,如 5s、100ms。 |
| 观察值 | d.count() | 返回底层的 tick 计数值(Rep 类型)。 |
| 算术运算 | d1 + d2, d1 - d2 | 时长之间加减,返回相同单位的时长。 |
| d * 3, d / 2 | 时长乘以或除以一个标量(Rep 类型)。 |
| 比较运算 | d1 == d2, d1 < d2 | 比较两个时长的长短(自动处理不同单位)。 |
| 单位转换 | duration_cast<seconds>(d) | 显式将时长转换为其他单位(可能截断)。 |
| duration<double>(d) | 若目标 Rep 为浮点数,可隐式转换(无需 cast)。 |
| 特殊值 | duration::zero() | 返回零长度的时长。 |
| duration::min() | 返回该时长类型能表示的最小值(最负值)。 |
| duration::max() | 返回该时长类型能表示的最大值。 |
#include<iostream>
#include<chrono>
#include <ratio>
using namespace std::chrono;
void TestDuration()
{
milliseconds ms(1024);
std::cout << ms.count() << " ms\n";
seconds s = duration_cast<seconds>(milliseconds(2500) + milliseconds(500));
std::cout << s.count() << " s\n";
if (minutes(1) > seconds(30)) {
std::cout << "1 分钟比 30 秒长\n";
}
seconds s2 = duration_cast<seconds>(milliseconds(1500));
std::cout << s2.count() << " s (截断后)\n";
duration<double, std::ratio<60>> d_min = seconds(90);
std::cout << d_min.count() << " 分钟\n";
std::cout << "duration<int>::zero(): " << duration<int>::zero().count() << "\n";
}
结果为:

std::chrono::treat_as_floating_point
这是一个类型特征(type trait),用于判断一个类型是否应被视为浮点数,从而决定是否允许从高精度到低精度的隐式转换。
template< class Rep > struct treat_as_floating_point : std::is_floating_point<Rep> {};
template< class Rep > constexpr bool treat_as_floating_point_v = treat_as_floating_point<Rep>::value;
它继承自 std::is_floating_point,因此默认情况下,只有当 Rep 是 float、double、long double 时,其值为 true。
| 表达式 | 值示例 | 作用与说明 |
|---|
treat_as_floating_point_v<int> | false | 指示 int 不被视为浮点数。 |
treat_as_floating_point_v<double> | true | 指示 double 被视为浮点数。 |
| 编译期分支 | if constexpr (treat_as_floating_point_v<Rep>) | 在模板编程中,根据此特征选择不同的实现路径。 |
| 控制隐式转换 | duration<double, milli> d = seconds(1); | 允许:因为目标 Rep 为 double,treat_as_floating_point 为 true,1 秒 隐式转为 1000.0 毫秒。 |
| duration<int, milli> d = seconds(1); | 禁止(编译错误):因为目标 Rep 为 int,treat_as_floating_point 为 false,隐式转换可能截断数据,必须用 duration_cast。 |
#include<iostream>
#include<chrono>
#include <ratio>
using namespace std::chrono;
void TestTAFP()
{
std::cout << std::boolalpha;
std::cout << "treat_as_floating_point<int>: " << treat_as_floating_point<int>::value << "\n";
std::cout << "treat_as_floating_point<double>: " << treat_as_floating_point<double>::value << "\n";
seconds s(1);
duration<double, std::milli> d1 = s;
std::cout << "d1 (double 毫秒): " << d1.count() << " ms\n";
duration<int, std::milli> d2 = duration_cast<milliseconds>(s);
std::cout << "d2 (int 毫秒,显式转换): " << d2.count() << " ms\n";
}
结果为:

std::chrono::duration_values
这是一个工具类模板,它为给定的 Rep 类型提供了获取零值、最小值和最大值的统一静态接口。
template<class Rep> struct duration_values;
| 静态函数 | 表达式示例 | 作用与说明 |
|---|
zero() | duration_values<Rep>::zero() | 返回 Rep 类型的零值表示。通常为 Rep(0)。 |
min() | duration_values<Rep>::min() | 返回 Rep 类型能表示的最小(最负)值。 |
max() | duration_values<Rep>::max() | 返回 Rep 类型能表示的最大值。 |
主要用途:它主要被标准库内部用于实现 duration::zero()、duration::min() 和 duration::max() 成员函数。例如,duration::zero() 的实现通常会返回 duration(duration_values<Rep>::zero())。
自定义类型支持:如果你想用自定义类型(如高精度有理数类)作为 duration 的 Rep,可以通过特化 duration_values 来告知标准库如何获取该类型的这些边界值,从而使你的类型能与 <chrono 无缝协作。
#include<iostream>
#include<chrono>
#include <ratio>
using namespace std::chrono;
void TestDurationV()
{
std::cout << "duration_values<int>::zero(): " << duration_values<int>::zero() << "\n";
std::cout << "duration_values<int>::min(): " << duration_values<int>::min() << "\n";
std::cout << "duration_values<int>::max(): " << duration_values<int>::max() << "\n";
std::cout << "duration_values<double>::zero(): " << duration_values<double>::zero() << "\n";
std::cout << "duration_values<double>::min(): " << duration_values<double>::min() << "\n";
std::cout << "duration_values<double>::max(): " << duration_values<double>::max() << "\n";
auto zero_dur1 = duration<int>::zero();
auto zero_dur2 = duration<int>(duration_values<int>::zero());
std::cout << "duration<int>::zero().count(): " << zero_dur1.count() << "\n";
std::cout << "手动构造的零时长 count(): " << zero_dur2.count() << "\n";
}
结果为:

时间点
time_point
这是最核心的类模板,它将一个时钟(Clock)和一个时长(Duration)结合起来,表示相对于该时钟纪元(epoch)的一个具体时间点。
template<class Clock, class Duration = typename Clock::duration> class time_point;
**Clock**:关联的时钟类型,如 system_clock、steady_clock。它定义了时间点的原点(纪元)。
**Duration**:用于度量从纪元到该时间点的时长的类型,默认为时钟的 duration 类型(通常是 microseconds 或 nanoseconds)。
| 接口类别 | 表达式/代码示例 | 作用与说明 |
|---|
| 构造函数 | time_point<Clock, Duration> tp; | 默认构造,创建一个值初始化的时间点(对于某些时钟,表示其纪元)。 |
| time_point<Clock, Duration> tp(d); | 用 duration(从纪元开始的时长)构造一个时间点。 |
| 获取时长 | tp.time_since_epoch() | 核心观察函数。返回一个 Duration 类型的值,表示从时钟纪元到 tp 的时间长度。 |
| 算术运算 | tp + d, tp - d | 时间点 ± 时长 = 新的时间点。d 是 duration 类型。 |
| tp1 - tp2 | 两个时间点相减,得到一个 duration 对象,表示它们之间的时间间隔。要求两个时间点使用相同的时钟。 |
| 比较运算 | tp1 == tp2, tp1 < tp2 | 比较两个时间点的先后顺序(要求相同时钟)。 |
| 单位转换 | time_point_cast<duration>(tp) | 将时间点的内部时长单位显式转换为另一种 duration 类型(可能截断)。 |
| floor<duration>(tp), ceil<duration>(tp) | (C++17/20) 将时间点向下、向上取整到指定的时长精度。 |
| 特殊值 | time_point::min() | 返回该时钟能表示的最早时间点(通常是非常久远的过去)。 |
| time_point::max() | 返回该时钟能表示的最晚时间点。 |
#include <iostream>
#include <chrono>
#include <thread>
using namespace std::chrono;
void TestTimePoint()
{
system_clock::time_point now = system_clock::now();
std::cout << "当前时间点已获取\n";
auto since_epoch = now.time_since_epoch();
std::cout << "从纪元至今已过去 " << duration_cast<seconds>(since_epoch).count() << " 秒\n";
system_clock::time_point one_hour_later = now + hours(1);
std::cout << "1 小时后的时间点已计算\n";
auto diff = one_hour_later - now;
std::cout << "时间间隔为 " << duration_cast<minutes>(diff).count() << " 分钟\n";
if (one_hour_later > now) {
std::cout << "1 小时后确实晚于现在\n";
}
auto now_ms = time_point_cast<milliseconds>(now);
std::cout << "当前时间点的毫秒精度计数:" << now_ms.time_since_epoch().count() << " ms\n";
auto start = steady_clock::now();
std::this_thread::sleep_for(milliseconds(100));
auto end = steady_clock::now();
std::cout << "休眠了 " << duration_cast<microseconds>(end - start).count() << " 微秒\n";
}
结果为:

clock_time_conversion
这是一个特性类(trait),它提供了在不同时钟的时间点之间进行转换的机制。在 C++20 中,随着新时钟(如 utc_clock)的引入,这个类变得尤为重要。标准库通过特化为特定的时钟对提供转换逻辑。
template<class Dest, class Source> struct clock_time_conversion;
Dest 和 Source:通常是时钟类型。这个模板本身没有定义通用的转换,而是通过为特定的时钟对提供特化来实现转换逻辑。
这个类的主要接口是一个函数调用运算符(operator()),它接受一个源时钟的时间点,并返回目标时钟的时间点(或是一个能转换为时间点的类型,如 sys_time)。
| 表达式 | 作用与说明 |
|---|
| 通用形式 | clock_time_conversion<Dest, Source>{} 创建一个转换器对象。 |
| converter(tp_source) 执行实际的转换。 |
| C++20 常用转换示例 | clock_time_conversion<system_clock, utc_clock>{}(utc_tp):将 utc_time 转换为 sys_time。 |
| clock_time_conversion<utc_clock, system_clock>{}(sys_tp):将 sys_time 转换为 utc_time。 |
| clock_time_conversion<system_clock, tai_clock>{}(tai_tp):将 tai_time 转换为 sys_time。 |
| 便捷函数 | clock_cast<Dest>(tp_source) (C++20):这是 clock_time_conversion 的简化包装,使用更直观。例如 clock_cast<utc_clock>(sys_tp)。 |
#include <iostream>
#include <chrono>
#include <thread>
using namespace std::chrono;
void TestCTC()
{
auto sys_now = system_clock::now();
utc_clock::time_point utc_now = clock_cast<utc_clock>(sys_now);
std::cout << "系统时间已转换为 UTC 时间\n";
clock_time_conversion<utc_clock, system_clock> converter;
utc_clock::time_point utc_now2 = converter(sys_now);
std::cout << "使用转换器再次转换成功\n";
auto sys_now2 = clock_cast<system_clock>(utc_now);
if (sys_now == sys_now2) {
std::cout << "往返转换后时间点一致\n";
}
tai_clock::time_point tai_now = clock_cast<tai_clock>(sys_now);
std::cout << "系统时间已转换为 TAI 时间\n";
auto offset = tai_now - clock_cast<tai_clock>(utc_now);
std::cout << "当前 UTC 与 TAI 偏移:" << duration_cast<seconds>(offset).count() << " 秒\n";
}
结果为:

时钟
在 <chrono 中,一个时钟类通常提供以下核心接口(以静态成员的形式):
| 接口 | 表达式 | 作用与说明 |
|---|
| 当前时间点 | clock::now() | 静态成员函数,返回当前时刻的 time_point。这是最常用的接口。 |
| 时间点类型 | clock::time_point | 该时钟专属的时间点类型,通常是 time_point<clock> 的别名。 |
| 时长类型 | clock::duration | 该时钟的'原生'精度(tick 周期),通常是 duration 的别名,如 microseconds。 |
| 周期类型 | clock::period | 即 duration 的 Period 模板参数,一个 ratio 类型,表示 tick 周期(秒)。 |
| 是否稳定 | clock::is_steady | 布尔值常量。true 表示该时钟是单调的(不会向后调整),适合测量时间间隔。 |
C++11 的基础三时钟
这三个时钟从 C++11 起就存在,覆盖了绝大多数日常使用场景。
| 时钟名称 | 核心特性 | 典型用途 | 是否稳定 (is_steady) |
|---|
system_clock | 表示系统实时时钟(挂钟时间),通常基于 Unix 时间戳(1970-01-01 00:00:00 UTC)。 | 获取当前日期时间、与日历交互、需要存储或展示给用户的时间。 | 通常为 false(管理员可修改系统时间) |
steady_clock | 单调时钟,保证只能向前移动,不受系统时间调整影响。 | 测量时间间隔、性能测试、超时计时。 | true |
high_resolution_clock | 当前设置下具有最高精度的时钟。通常是 system_clock 或 steady_clock 的别名。 | 需要最高精度计时的场景(但请注意,精度和稳定性需查阅具体实现)。 | 取决于其底层别名 |
#include <iostream>
#include <chrono>
#include <thread>
using namespace std::chrono;
void TestClocks11()
{
auto sys_now = system_clock::now();
std::time_t sys_now_c = system_clock::to_time_t(sys_now);
std::cout << "system_clock 当前时间:" << std::ctime(&sys_now_c);
auto start = steady_clock::now();
std::this_thread::sleep_for(milliseconds(100));
auto end = steady_clock::now();
auto elapsed = duration_cast<microseconds>(end - start);
std::cout << "steady_clock 测量耗时:" << elapsed.count() << " us\n";
auto hr_start = high_resolution_clock::now();
auto hr_end = high_resolution_clock::now();
auto hr_elapsed = duration_cast<nanoseconds>(hr_end - hr_start);
std::cout << "high_resolution_clock 耗时:" << hr_elapsed.count() << " ns\n";
std::cout << std::boolalpha;
std::cout << "system_clock 是否稳定:" << system_clock::is_steady << "\n";
std::cout << "steady_clock 是否稳定:" << steady_clock::is_steady << "\n";
}
结果为:

C++20 新增的专业时钟
C++20 极大地扩展了时钟家族,引入了处理不同时间标准的能力,主要面向科学计算、卫星通信等专业领域。
| 时钟名称 | 核心特性与用途 |
|---|
utc_clock | 协调世界时(UTC)。它处理跳秒(leap second),能精确表示闰秒时刻。now() 返回 utc_time。 |
tai_clock | 国际原子时(TAI)。它是连续的时间尺度,没有跳秒,与 UTC 有固定偏移(当前为 37 秒)。 |
gps_clock | 全球定位系统时(GPS)。它与 TAI 有固定偏移(TAI = GPS + 19 秒),起点为 1980-01-06。 |
file_clock | 用于文件系统时间戳的时钟别名。它提供了与 system_clock 相互转换的便捷方法 to_sys()/from_sys(),以处理不同操作系统的文件时间纪元。 |
local_t | 这是一个伪时钟(pseudo-clock),它本身不提供 now() 函数。它用于标记一个时间点是与本地时间(未指定时区)关联的,需要配合时区库(如 zoned_time)才有实际意义。 |
#include <iostream>
#include <chrono>
#include <thread>
using namespace std::chrono;
void TestClocks20()
{
auto utc_now = utc_clock::now();
std::cout << "UTC 时间已获取\n";
auto tai_now = tai_clock::now();
std::cout << "TAI 时间已获取\n";
auto sys_from_utc = clock_cast<system_clock>(utc_now);
auto sys_from_tai = clock_cast<system_clock>(tai_now);
std::cout << "UTC 和 TAI 时间已转换为 system_clock\n";
auto file_now = file_clock::now();
auto sys_from_file = clock_cast<system_clock>(file_now);
std::cout << "文件时间已转换为系统时间\n";
}
示例代码:

辅助模板:is_clock 与 is_clock_v
这是 C++20 引入的一个类型特征,用于在编译期判断一个类型是否符合时钟的要求。
| 表达式 | 作用与说明 |
|---|
is_clock<T>::value | 如果类型 T 满足时钟的所有要求(嵌套类型 duration、rep、period、time_point 以及静态函数 now() 等),则为 true。 |
is_clock_v<T> | 辅助变量模板,等同于 is_clock<T>::value。 |
#include <iostream>
#include <chrono>
#include <thread>
using namespace std::chrono;
class MYclass {};
void TestCheck()
{
std::cout << std::boolalpha;
std::cout << "is_clock_v<system_clock>: " << std::chrono::is_clock_v<std::chrono::system_clock> << "\n";
std::cout << "is_clock_v<MYclass>: " << std::chrono::is_clock_v<MYclass> << "\n";
}
结果为:

日历
日历基础类型
这些是构成日期的基本原子类型,每个都封装了特定的日历字段。
| 类型名称 | 引入标准 | 核心接口与表达式 | 作用与说明 |
|---|
day | C++20 | day d(21); | 构造一个表示某日的对象(1-31)。 |
| | d.ok() | 检查该日是否有效(如 2 月 30 日返回 false)。 |
| | d == day(21) | 比较两个日是否相等。 |
| | d += days(1) | 日期的加减运算(会进位到月份)。 |
month | C++20 | month m(5); 或 month m = May; | 构造一个表示某月的对象(1-12)。标准库提供了 January 到 December 常量。 |
| | m.ok() | 检查月份是否在有效范围(1-12)。 |
| | m == May | 比较月份。 |
year | C++20 | year y(2024); 或 2024y(字面量) | 构造一个表示年份的对象。 |
| | y.is_leap() | 判断该年是否为闰年。 |
| | y.ok() | 检查年份是否在 [-32767, 32767] 范围内(通常够用了)。 |
weekday | C++20 | weekday wd(3); 或 wd = Wednesday; | 构造一个表示星期几的对象(0=周日,1=周一...6=周六)。有 Sunday..Saturday 常量。 |
| | wd.c_encoding() | 返回 C 风格的编码(0-6,周日为 0)。 |
| | wd.iso_encoding() | 返回 ISO 风格的编码(1-7,周一为 1,周日为 7)。 |
| | wd.ok() | 检查是否有效。 |
last_spec | C++20 | last(单例常量) | 一个标签类型,用于表示'月份的最后一天'或'最后一个 weekday'。例如 May/last 表示 5 月 31 日。 |
日历复合类型
| 类型名称 | 核心接口与表达式 | 作用与说明 |
|---|
month_day | month_day md = May/21d; | 表示'月/日'组合(无年份)。用于描述每年的固定日期,如圣诞节。 |
| md.ok() | 检查是否有效(如 2 月 30 日无效)。 |
month_day_last | month_day_last mdl = May/last; | 表示某个月的最后一天(无年份)。 |
year_month | year_month ym = 2024y/May; | 表示'年/月'组合。 |
| ym.ok() | 检查年月是否有效。 |
| ym + months(1) | 加减月份,自动处理年份进位。 |
year_month_day | year_month_day ymd = 2024y/May/21d; | 最常用的完整日期类型。 |
| ymd.ok() | 检查日期是否有效(处理闰年等)。 |
| ymd.year(), ymd.month(), ymd.day() | 分别获取年、月、日字段。 |
| sys_days{ymd} | 转换为 system_clock 的时间点(基于 days 精度),便于与旧代码交互。 |
year_month_day_last | year_month_day_last ymdl = 2024y/May/last; | 表示某年某月的最后一天。 |
| ymdl.day() | 获取该月的最后一天是几号(例如 2024y/February/last 返回 29)。 |
year_month_weekday | year_month_weekday ymwd = 2024y/May/Wednesday[3]; | 表示'某年某月的第 N 个星期 W',如 2024 年 5 月的第三个星期三。 |
year_month_weekday_last | year_month_weekday_last ymwdl = 2024y/May/Wednesday[last]; | 表示'某年某月的最后一个星期 W'。 |
month_weekday | month_weekday mwd = May/Wednesday[3]; | 表示'某月的第 N 个星期 W'(无年份)。 |
month_weekday_last | month_weekday_last mwdl = May/Wednesday[last]; | 表示'某月的最后一个星期 W'(无年份)。 |
关键运算符与函数
日历类型之所以强大,很大程度上归功于重载的运算符。
| 类别 | 表达式 | 作用与说明 |
|---|
| 创建运算符 | / | 最重要的创建语法。/ 被重载用于组合年、月、日,顺序任意,如 2024y/May/21d、21d/May/2024、May/21d/2024 均合法。 |
| 算术运算 | +, - | 可以对 year_month 加减 months 或 years;对 year_month_day 加减 days、months 或 years。 |
| 比较运算 | ==, !=, <, <=, >, >= | 所有日历类型都支持全系列的比较运算符,按日历顺序比较。 |
| 差值运算 | - | 两个 year_month_day 相减,返回 days 类型的时长。 |
| 流输出 | << | 大多数日历类型都重载了 <<,可以直接 std::cout << ymd;,输出格式为 YYYY-MM-DD。 |
代码示例:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <chrono>
using namespace std::chrono;
using namespace std::literals;
int main()
{
auto ymd1 = 2024y / May / 21d;
auto ymd2 = 21d / May / 2024;
auto ymd3 = year_month_day{ 2024y, May, 21d };
std::cout << "ymd1 = " << ymd1 << "\n";
std::cout << "ymd2 = " << ymd2 << "\n";
std::cout << "ymd3 = " << ymd3 << "\n";
std::cout << "年份:" << ymd1.year() << "\n";
std::cout << "月份:" << ymd1.month() << "\n";
std::cout << "日:" << ymd1.day() << "\n";
auto ymd_next_month = ymd1 + months(1);
std::cout << "下个月:" << ymd_next_month << "\n";
auto ymd_next_year = ymd1 + years(1);
std::cout << "明年:" << ymd_next_year << "\n";
auto ymd_next_week = year_month_day{ sys_days{ymd1} + days(7) };
std::cout << "一周后:" << ymd_next_week << "\n";
auto last_day_of_feb_2024 = 2024y / February / last;
std::cout << "2024 年 2 月最后一天:" << last_day_of_feb_2024 << "\n";
std::cout << "那天是星期几?" << weekday{ last_day_of_feb_2024 } << "\n";
auto third_wed_may_2024 = 2024y / May / Wednesday[3];
std::cout << "2024 年 5 月第三个星期三:" << third_wed_may_2024 << "\n";
year_month_day invalid_date = 2024y / February / 30d;
std::cout << "2024-02-30 有效吗?" << std::boolalpha << invalid_date.ok() << "\n";
sys_days tp = sys_days{ ymd1 };
std::cout << "时间点 (自纪元天数): " << tp.time_since_epoch().count() << " 天\n";
auto xmas = 2024y / December / 25d;
auto days_until_xmas = sys_days{ xmas } - sys_days{ ymd1 };
std::cout << "距离 2024 年圣诞节还有:" << days_until_xmas.count() << " 天\n";
weekday today = weekday{ ymd1 };
weekday fri = Friday;
auto days_until_fri = (fri - today).count();
if (days_until_fri <= 0) days_until_fri += 7;
std::cout << "下一个周五在 " << days_until_fri << " 天后\n";
return 0;
}
结果为:

函数
时长(Duration)操作函数
这些函数用于操作 duration 对象,进行转换、取整和算术运算。
| 函数表达式 | 引入标准 | 简要说明 |
|---|
duration_cast<ToDuration>(d) | C++11 | 将时长 d 转换为其他单位,截断取整(向零)。 |
floor<ToDuration>(d) | C++17 | 将时长 d 向下取整(负无穷方向)到目标单位。 |
ceil<ToDuration>(d) | C++17 | 将时长 d 向上取整(正无穷方向)到目标单位。 |
round<ToDuration>(d) | C++17 | 将时长 d 四舍五入到目标单位,中间情况向偶数舍入。 |
abs(d) | C++17 | 返回时长 d 的绝对值。 |
d1 + d2, d1 - d2 | C++11 | 时长之间的加减法。 |
d * n, n * d, d / n | C++11 | 时长与标量的乘除法。 |
d1 == d2, d1 < d2, 等 | C++11 | 时长之间的比较运算符。 |
时间点(Time Point)操作函数
这些函数用于操作 time_point 对象,进行算术、转换和取整。
| 函数表达式 | 引入标准 | 简要说明 |
|---|
time_point_cast<ToDuration>(tp) | C++11 | 将时间点 tp 的内部时长单位转换为另一种 duration(截断)。 |
floor<ToDuration>(tp) | C++17 | 将时间点 tp 向下取整到指定的时长精度。 |
ceil<ToDuration>(tp) | C++17 | 将时间点 tp 向上取整到指定的时长精度。 |
round<ToDuration>(tp) | C++17 | 将时间点 tp 四舍五入到指定的时长精度。 |
tp + d, tp - d | C++11 | 时间点与时长相加/减,得到新的时间点。 |
tp1 - tp2 | C++11 | 两个时间点相减,得到 duration 间隔。 |
tp1 == tp2, tp1 < tp2, 等 | C++11 | 时间点之间的比较运算符。 |
clock_cast<DestClock>(tp) | C++20 | 将时间点在不同时钟(如 system_clock、utc_clock)之间转换。 |
日历(Calendar)操作函数
C++20 引入,用于操作日期类型,提供算术和比较功能。每个主要类型都有一组对应的运算符。
| 函数表达式类别 | 涉及类型示例 | 简要说明 |
|---|
| 算术运算 | day + days, month + months, year + years, year_month + months, year_month_day + years/months, 等 | 对日历类型进行加减运算,自动处理进位(如月份加 1 跨年)。 |
| 比较运算 | day == day, month != month, year < year, year_month_day <= year_month_day, 等 | 所有日历类型都支持完整的比较运算符。 |
| 差值运算 | year_month_day - year_month_day | 返回 days 类型,表示两个日期的天数差。 |
| 创建语法 | year/month/day, day/month/year, month/day/year | 重载的 / 运算符,用于直观地创建日期组合。 |
输入/输出(I/O)函数
这些函数用于将 <chrono 类型格式化输出到流,或从流解析。
| 函数表达式 | 引入标准 | 简要说明 |
|---|
operator<<(os, duration) | C++11 | 输出时长(如 "5s")。 |
operator<<(os, sys_time), operator<<(os, utc_time), 等 | C++20 | 输出各种时钟的时间点。 |
operator<<(os, day), operator<<(os, month), operator<<(os, year), operator<<(os, year_month_day), 等 | C++20 | 输出所有日历类型。 |
operator<<(os, hh_mm_ss) | C++20 | 输出时刻(如 "12:30:45.123")。 |
operator<<(os, sys_info), operator<<(os, local_info) | C++20 | 输出时区信息。 |
from_stream(is, fmt, day), from_stream(is, fmt, year_month_day), 等 | C++20 | 从输入流按照指定格式解析日历类型。 |
parse(fmt, tp) | C++20 | 返回一个用于解析的占位符,常与 from_stream 配合。 |
字面量(Literals)运算符
定义在内联命名空间 std::literals::chrono_literals 中,用于便捷书写时长和年份。
| 字面量运算符 | 引入标准 | 返回类型/说明 |
|---|
operator""h | C++14 | 返回 hours(小时时长) |
operator""min | C++14 | 返回 minutes(分钟时长) |
operator""s | C++14 | 返回 seconds(秒时长) |
operator""ms | C++14 | 返回 milliseconds(毫秒时长) |
operator""us | C++14 | 返回 microseconds(微秒时长) |
operator""ns | C++14 | 返回 nanoseconds(纳秒时长) |
operator""y | C++20 | 返回 year(年份) |
测试
日期计算
#include <chrono>
#include <iostream>
#include <string>
#include< cassert >
class Date {
private:
std::chrono::year_month_day ymd_;
public:
Date(const std::chrono::year_month_day& ymd) : ymd_(ymd) {
if (!ymd_.ok()) {
throw std::invalid_argument("Invalid date");
}
}
Date(int year, int month, int day) : Date(std::chrono::year{ year } / month / day) { }
static Date today() {
auto now = std::chrono::system_clock::now();
auto today_days = std::chrono::floor<std::chrono::days>(now);
return Date{ std::chrono::year_month_day{today_days} };
}
int year() const { return static_cast<int>(ymd_.year()); }
unsigned month() const { return static_cast<unsigned>(ymd_.month()); }
unsigned day() const { return static_cast<unsigned>(ymd_.day()); }
unsigned weekday() const {
std::chrono::weekday wd{ std::chrono::sys_days{ymd_} };
return wd.c_encoding();
}
bool isLeapYear() const { return ymd_.year().is_leap(); }
Date operator+(const std::chrono::days& d) const {
auto sd = std::chrono::sys_days{ ymd_ } + d;
return Date{ std::chrono::year_month_day{sd} };
}
Date operator-(const std::chrono::days& d) const { return *this + (-d); }
Date operator+(const std::chrono::months& m) const {
auto new_ymd = ymd_ + m;
if (!new_ymd.ok()) {
new_ymd = new_ymd.year() / new_ymd.month() / std::chrono::last;
}
return Date{ new_ymd };
}
Date operator-(const std::chrono::months& m) const { return *this + (-m); }
Date operator+(const std::chrono::years& y) const {
auto new_ymd = ymd_ + y;
if (!new_ymd.ok()) {
new_ymd = new_ymd.year() / new_ymd.month() / std::chrono::last;
}
return Date{ new_ymd };
}
Date operator-(const std::chrono::years& y) const { return *this + (-y); }
Date& operator+=(const std::chrono::days& d) { return *this = *this + d; }
Date& operator-=(const std::chrono::days& d) { return *this = *this - d; }
Date& operator+=(const std::chrono::months& m) { return *this = *this - m; }
Date& operator-=(const std::chrono::months& m) { return *this = *this - m; }
Date& operator+=(const std::chrono::years& y) { return *this = *this + y; }
Date& operator-=(const std::chrono::years& y) { return *this = *this - y; }
auto operator<=>(const Date&) const = default;
std::chrono::days daysUntil(const Date& other) const {
return std::chrono::sys_days{ other.ymd_ } - std::chrono::sys_days{ ymd_ };
}
int monthsUntil(const Date& other) const {
int y1 = year(), m1 = month(), d1 = day();
int y2 = other.year(), m2 = other.month(), d2 = other.day();
int month_diff = (y2 - y1) * 12 + (m2 - m1);
if (d2 < d1) {
--month_diff;
}
return month_diff;
}
friend std::ostream& operator<<(std::ostream& os, const Date& date) {
os << date.year() << '-' << (date.month() < 10 ? "0" : "") << date.month() << '-' << (date.day() < 10 ? "0" : "") << date.day();
return os;
}
const std::chrono::year_month_day& get_ymd() const { return ymd_; }
};
#include"Date.h"
using namespace std::chrono_literals;
void testDate() {
std::cout << "=== 开始测试 Date 类 ===\n";
std::cout << "1. 构造函数测试:\n";
Date d1(2024, 5, 21);
std::cout << " d1: " << d1 << "\n";
assert(d1.year() == 2024 && d1.month() == 5 && d1.day() == 21);
auto d_today = Date::today();
std::cout << " Today: " << d_today << "\n";
std::cout << "2. 无效日期测试 (2024-02-30): ";
try {
Date invalid(2024, 2, 30);
std::cout << "FAILED: 未抛出异常\n";
} catch (const std::invalid_argument& e) {
std::cout << "PASSED: 捕获异常 - " << e.what() << "\n";
} catch (...) {
std::cout << "FAILED: 捕获到未知异常\n";
}
std::cout << "3. 闰年判断测试:\n";
assert(Date(2024, 1, 1).isLeapYear() == true);
assert(Date(2023, 1, 1).isLeapYear() == false);
assert(Date(2000, 1, 1).isLeapYear() == true);
assert(Date(1900, 1, 1).isLeapYear() == false);
std::cout << " 所有闰年判断通过\n";
std::cout << "4. 星期几测试:\n";
assert(Date(2024, 5, 21).weekday() == 2);
assert(Date(2024, 5, 22).weekday() == 3);
assert(Date(2024, 5, 19).weekday() == 0);
std::cout << " 所有星期几判断通过\n";
std::cout << "5. 日期算术测试:\n";
Date d2(2024, 5, 21);
auto d_plus_7 = d2 + std::chrono::days(7);
std::cout << " " << d2 << " + 7 days = " << d_plus_7 << "\n";
assert(d_plus_7 == Date(2024, 5, 28));
auto d_plus_1m = d2 + std::chrono::months(1);
std::cout << " " << d2 << " + 1 month = " << d_plus_1m << "\n";
assert(d_plus_1m == Date(2024, 6, 21));
auto d_plus_1y = d2 + std::chrono::years(1);
std::cout << " " << d2 << " + 1 year = " << d_plus_1y << "\n";
assert(d_plus_1y == Date(2025, 5, 21));
Date d3(2024, 1, 31);
auto d_plus_1m_jan = d3 + std::chrono::months(1);
std::cout << " " << d3 << " + 1 month = " << d_plus_1m_jan << "\n";
assert(d_plus_1m_jan == Date(2024, 2, 29));
Date d4(2023, 1, 31);
auto d_plus_1m_jan_nonleap = d4 + std::chrono::months(1);
std::cout << " " << d4 << " + 1 month = " << d_plus_1m_jan_nonleap << "\n";
assert(d_plus_1m_jan_nonleap == Date(2023, 2, 28));
Date d5(2024, 12, 15);
auto d_plus_2m = d5 + std::chrono::months(2);
std::cout << " " << d5 << " + 2 months = " << d_plus_2m << "\n";
assert(d_plus_2m == Date(2025, 2, 15));
std::cout << "6. 比较运算符测试:\n";
Date d6a(2024, 5, 21);
Date d6b(2024, 5, 22);
Date d6c(2024, 5, 21);
assert(d6a == d6c);
assert(d6a != d6b);
assert(d6a < d6b);
assert(d6a <= d6c);
assert(d6b > d6a);
assert(d6b >= d6c);
std::cout << " 所有比较运算符通过\n";
std::cout << "7. 差值计算测试:\n";
Date d7_start(2024, 5, 21);
Date d7_end(2024, 12, 25);
auto days_diff = d7_start.daysUntil(d7_end);
std::cout << " " << d7_start << " 到 " << d7_end << " 相差 " << days_diff.count() << " 天\n";
assert(days_diff.count() == 218);
auto months_diff = d7_start.monthsUntil(d7_end);
std::cout << " " << d7_start << " 到 " << d7_end << " 相差 " << months_diff << " 个整月\n";
assert(months_diff == 7);
Date d7_start2(2024, 12, 25);
Date d7_end2(2025, 1, 10);
auto days_diff2 = d7_start2.daysUntil(d7_end2);
std::cout << " " << d7_start2 << " 到 " << d7_end2 << " 相差 " << days_diff2.count() << " 天\n";
assert(days_diff2.count() == 16);
std::cout << "8. 复合赋值测试:\n";
Date d8(2024, 5, 21);
d8 += std::chrono::days(10);
std::cout << " +=10 天:" << d8 << "\n";
assert(d8 == Date(2024, 5, 31));
d8 += std::chrono::months(1);
std::cout << " +=1 月:" << d8 << "\n";
assert(d8 == Date(2024, 6, 30));
d8 -= std::chrono::years(1);
std::cout << " -=1 年:" << d8 << "\n";
assert(d8 == Date(2023, 6, 30));
std::cout << "=== 所有测试通过 ===\n";
}
int main() {
testDate();
return 0;
}
结果为:

性能测试
本期我们以内存池为例,对比其与直接向系统 new/delete 的性能差距
#pragma once
#include <vector>
#include <cassert>
#include <iostream>
using std::vector;
using std::cout;
using std::endl;
namespace MyMemoryPool {
constexpr inline size_t align_up(size_t n, size_t align) {
return (n + (align - 1)) & ~(align - 1);
}
class FixedSizePool {
public:
explicit FixedSizePool(size_t blockSize, size_t blockPerPage = 1024)
: block_Size(adjust_block_size(blockSize)),
block_Per_Page(blockPerPage),
free_list(nullptr),
total_allocations(0),
total_deallocations(0),
page_count(0) {}
~FixedSizePool() {
for (void* page : Pages) {
::operator delete[](page);
}
}
void* allocate() {
if (!free_list)
{
expand();
}
Node* head = free_list;
free_list = head->next;
++total_allocations;
return head;
}
void deallocate(void* p) {
if (!p) return;
Node* node = static_cast<Node*>(p);
node->next = free_list;
free_list = node;
++total_deallocations;
}
size_t Get_block_Size() const { return block_Size; }
size_t Get_block_Per_Page() const { return block_Per_Page; }
void print_stats() const {
std::cout << "内存池统计:" << std::endl;
std::cout << " 总分配次数:" << total_allocations << std::endl;
std::cout << " 总释放次数:" << total_deallocations << std::endl;
std::cout << " 当前使用中:" << (total_allocations - total_deallocations) << std::endl;
std::cout << " 页数量:" << page_count << std::endl;
std::cout << " 内存使用量:" << (page_count * block_Size * block_Per_Page) << " 字节" << std::endl;
}
private:
struct Node {
Node* next;
};
void expand() {
size_t page_bytes = block_Size * block_Per_Page;
char* page = static_cast<char*>(::operator new[](page_bytes));
Pages.push_back(page);
++page_count;
for (size_t i = 0; i < block_Per_Page; ++i) {
char* block = page + i * block_Size;
Node* n = reinterpret_cast<Node*>(block);
n->next = free_list;
free_list = n;
}
}
size_t adjust_block_size(size_t s) const {
size_t min_size = sizeof(void*);
return align_up(s < min_size ? min_size : s, alignof(void*));
}
Node* free_list;
size_t block_Size;
size_t block_Per_Page;
vector<void*> Pages;
size_t total_allocations;
size_t total_deallocations;
size_t page_count;
};
}
#include <iostream>
#include <chrono>
#include <vector>
#include "MemoryPool.h"
using namespace MyMemoryPool;
using namespace std::chrono;
template <typename AllocFunc, typename DeallocFunc>
double run_test(size_t iterations, AllocFunc alloc, DeallocFunc dealloc) {
std::vector<void*> pointers;
pointers.reserve(iterations);
auto start = high_resolution_clock::now();
for (size_t i = 0; i < iterations; ++i) {
pointers.emplace_back(alloc());
}
for (void* p : pointers) {
dealloc(p);
}
auto end = high_resolution_clock::now();
duration<double> elapsed = end - start;
return elapsed.count();
}
int main() {
const size_t BLOCK_SIZE = 256;
const size_t BLOCKS_PER_PAGE = 1024;
const size_t ITERATIONS = 1000000;
std::cout << "===== 性能对比测试 =====\n";
std::cout << "块大小:" << BLOCK_SIZE << " 字节\n";
std::cout << "每页块数:" << BLOCKS_PER_PAGE << "\n";
std::cout << "分配/释放次数:" << ITERATIONS << "\n\n";
{
FixedSizePool pool(BLOCK_SIZE, BLOCKS_PER_PAGE);
double pool_time = run_test(ITERATIONS, [&pool]() { return pool.allocate(); }, [&pool](void* p) { pool.deallocate(p); });
std::cout << "内存池耗时:" << pool_time << " 秒\n";
pool.print_stats();
}
std::cout << "\n";
{
double new_time = run_test(ITERATIONS, []() { return ::operator new(BLOCK_SIZE); }, [](void* p) { ::operator delete(p); });
std::cout << "new/delete 耗时:" << new_time << " 秒\n";
}
return 0;
}
结果为:

掌握 chrono 库不仅能提升时间计算的准确性,更是进行底层性能剖析的关键工具。熟练运用它可以在时间计算、性能测试等多方面领域大展身手!
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online