std::chrono 是一个与时间运算相关的模块,是 C++11 引入的,它提供了一系列函数和类都处于 std::chrono 命名空间下。

使用该库需要了解它如下三个核心概念,了解了之后便可灵活运用。

  • duration: 时间段
  • time_point: 时间点
  • clock: 时钟

由于每次都写 std::chrono 这个很长的命名空间略显冗余,后文中只写 chrono

时间段

时间段的表示

chrono::duration 用来表示一段时间,1小时,12分钟,0.01 秒都可以表示。 不同的时间单位都能换算成秒,duration 正是通过秒来表示其他的时间段的。

chrono::duration<int, std::ratio<3600, 1>> two_hours(2);

上面的代码定义的时间段表示 2 个小时,这很难理解吧。看我慢慢解释。

duration 是一个模板类,它的定义如下:

template<typename Rep, typename Period = std::ratio<1>>
class duration;

template<std::intmax_t Num, std::intmax_t Denom = 1>
class ratio;

Period 是一个 std::ratio 类型, std::ratio 表示一个分数,第一个参数是分子 第二个是分子。duration<Rep, Period> 就表示这个时间段有 Period 秒。 模板参数 Rep 是一种数据类型,用来表示 Period 的数量。

chrono::duration<int, std::ratio<60, 1>> minute(1) // 1 分钟
chrono::duration<int, std::ratio<1, 1000>> ms(2) // 2 毫秒

chrono::duration<int, ratio<1, 1>> n_second(22); // 22秒
chrono::duration<double, ratio<60, 1>> n_minute(n_second); // 用 22 秒初始化此时间段
// duration.count() 用于返回该时间段的数量,比如有多少个一分钟,几个一小时
cout << n_minute.count() << '\n';  // 0.366667 - 22 秒为 0.367 分钟

经过上面的分析,应该已经明白时间段是如何表示的了吧。

chrono 中定义了常见的 duration 类型,下至纳秒上至小时:

chrono::nanoseconds
chrono::microseconds;
chrono::milliseconds;
chrono::seconds;
chrono::minutes;
chrono::hours;

时间段的转换

前面的例子中,我用 22 秒来初始化浮点表示的分钟,这是可行的。但是如果分钟也是用整形表示的,由于会损失精度,此时整形表示的分钟无法用 秒来表示。

chrono::duration<int, ratio<1, 1>> n_second(22); // 22秒
chrono::duration<double, ratio<60, 1>> n_minute(n_second); // 用 22 秒初始化此时间段

chrono::duration<int, ratio<60, 1>> n_minute(n_second); // 不行,不能通过编译

但有的时候有确实需要这样的转换,此时可以使用 duration_cast,此时会损失精度。

chrono::seconds n_second(122); // 122秒
chrono::minutes n_minute = chrono::duration_cast<chrono::minutes>(n_second);
cout << n_minute.count() << '\n';  // 2 分钟

时间段的运算

不同类型的时间段间可以进行加减乘除取模等操作,比如一小时加一分钟,一小时除以一秒钟,一小时除以 4。运算结果是符合直觉的。 即时间段与时间段相加自然还是时间段,而相除的结果自然是数值了。

chrono::seconds n_second(12);
chrono::minutes n_minute(1);
auto sum = n_minute + n_second;
cout << sum.count() << '\n';  // 72s

时间点

时间点的表示

时间点,顾名思义表示某个时刻。它的定义如下:

template<typename Clock, typename Duration = typename Clock::duration>
class time_point;

其中 Clock 表示一个时钟,下一节介绍,可想象家里挂了好多个时钟,这里指明哪一个时钟的时间。Duration 表示从时钟的起始点开始经过的时间。 下面是个例子:

chrono::time_point<chrono::system_clock, chrono::seconds> tp(chrono::seconds(1));

上面的时间点表示时间起点后的一秒。通常是 1970-01-01 00:00:01

时间点的运算

一个很有用的时间点就是当前时间点,可以通过如下方式获得:

chrono::system_clock::time_point now = chrono::system_clock::now();

时间点之间可以做减法得到时间段,时间点加时间段自然就是新的时间点了。

chrono::system_clock::time_point now = chrono::system_clock::now();
auto tomorrow = now + chrono::hours(24); // 明日此时

// 获得时间点距离时钟起始时刻的 duration
decltype(now)::duration dur = now.time_since_epoch();
cout << dur.count();

epoch 的意思是纪元,也就是某个时期的开始。不同的时钟可能对 epoch 选择不同,system_clock 选择的是 1970-01-01 00:00:00

时间点之间可能采用的 duration 不同,此时可以使用 time_point_cast 进行不同 duration 间的转换。

chrono::time_point<chrono::system_clock> tp(chrono::hours(1));
auto time_point_sec = chrono::time_point_cast<chrono::seconds>(tp);
cout << time_point_sec.time_since_epoch().count();

转换的时候存在精度丢失的问题,比如把分钟表示的时间点转为小时,零头就没了。

时钟

标准库中定义了大量的时钟,在 C++ 11 中存在下面三个时钟:

  • chrono::system_clock
  • chrono::steady_clock
  • chrono::high_resolution_clock: 只是 system_clock 的别名罢了。

system_clock

系统时钟,其 epoch 为 1970-01-01 00:00:00,通常用于表示具体的日期。它具有一下实用方法:

// 当前时间点
chrono::system_clock::time_point now = chrono::system_clock::now();

// 转换为 time_t 类型
time_t now_t = chrono::system_clock::to_time_t(now);

// 从 time_t 转为 time_point
time_t t = time(nullptr);
chrono::system_clock::time_point now = chrono::system_clock::from_time_t(t);

system_clock 提供了静态方法,可以获取当前时间,并且提供了与 time_t 类型的相互转化的接口。这样就可以利用 ctime 函数库中 时间格式化、转换时区等接口了。

steady_clock

字面上的意思是稳定的时钟,什么意思呢。此时钟的 epoch 是开机时间,因此,就算用户修改了系统事件,这个时钟也不会受到影响。它的计时始终是 增加的。但是 system_clock 就不同了,如果把时间向前设置到 2000 年,那么获取到的系统时间一下子就比先前小了。

下面例子中取 steady_clock 的当前时间,然后看看它距离 epoch 的时间长短:

auto now = chrono::steady_clock::now().time_since_epoch();

using hours = chrono::duration<double, ratio<3600, 1>>;

hours now_h = chrono::duration_cast<hours>(now);

cout << now_h.count(); // 79.66 - 说明我的机器开机 79.66 个小时了

此时钟常常用来对程序运行时间进行计时,或设置定时器的定时时长:

// 代码运行时间
chrono::system_clock::time_point start = chrono::steady_clock::now();
sleep(2);
chrono::system_clock::time_point stop = chrono::steady_clock::now();

chrono::nanoseconds ns = stop - start;
cout << ns.count(); // 约 2 000 000 000 ns

steady_clock 没有与 time_t 相互转换的接口,这很好理解,因为它不是干那事的。