C++11 chrono库处理日期和时间
C++11提供chrono库,可以很方便地用于处理日期和时间。
chrono库主要包含3种类型:时间间隔duration、时钟clocks、时间点time point。
duration:记录时间长度
duration 表示一段时间间隔,用来记录时间长度,可以表示几秒、几分钟,或几个小时的时间间隔。
原型:
#include <chrono>
template<class Rep, class Period = std::ratio<1, 1>>
class duration;
模板参数
Rep 是一个数值类型,表示时钟数的类型;
Period 是一个默认模板参数std::ratio,表示时钟周期。其原型如下:
template<std::intmax_t Num, std::intmax_t Denom = 1>
class ratio;
ratio 表示每个时钟周期的描述,其中第一个模板参数Num代表分子,Denom代表分母,分母默认1。因此,ratio代表的是一个分子除以分母的分数值,比如,
1)ratio<2>代表一个时钟周期是2秒,因为2 / 1 = 2秒;
2)ratio<6060>代表6060秒,即1小时;
3)ratio<606024>代表606024秒,即1天;
4)ratio<1, 1000>代表1/1000秒,即1毫秒;
5)ratio<1, 1000000>代表1/1000000秒,即1微妙;
6)ratio<1, 1000000000>代表1/1000000000秒,即1纳秒;
标准库定义的常用时间间隔
为方便使用,标准库定义了一些常用时间间隔,如时、分、秒、毫秒、微妙、纳秒。在chrono命名空间下,其定义如下:
typedef duration<Rep, ratio<3600, 1>> hours;
typedef duration<Rep, ratio<60, 1>> minites;
typedef duration<Rep, ratio<1, 1>> seconds;
typedef duration<Rep, ratio<1, 1000>> milliseconds;
typedef duration<Rep, ratio<1, 1000000>> microseconds;
typedef duration<Rep, ratio<1, 1000000000>> nanoseconds;
示例
指定线程休眠时间
时间间隔类型常用来搭配this_thread::sleep_for使用,指定线程休眠时间。
chrono还提供了用于获取时间间隔的时钟周期数的方法count()。
#include <chrono>
#include <iostream>
int main()
{
std::chrono::milliseconds ms(3); // 3ms
std::chrono::microseconds us = 2 * ms; // 6ms = 6000us
// 30Hz clock using fractional ticks
std::chrono::duration<double, std::ratio<1, 30>> hz30{3.5}; // 3.5 * 1/30Hz
std::cout << "3 ms duration has " << ms.count() << " ticks\n" << "6000 us duration has " << us.count() << " ticks\n";
return 0;
}
运行结果:
3 ms duration has 3 ticks
6000 us duration has 6000 ticks
计算时间间隔
2个时间间隔之间可以做差值运算,结果也是表示时间间隔。
比如,
std::chrono::minutes t1(10);
std::chrono::seconds t2(60);
std::chrono::seconds t3 = t1 - t2;
std::cout << t3.count() << " second" << std::endl; // 打印 540 second
其中,t1表示10分子,t2表示60秒,t3为t1-t2,即600 - 60 = 540秒。调用t3的count,输出差值为540个时钟周期(因为t3的ratio分母代表1秒)。
当对两个duration做加减运算,它们的时钟周期不相同时,会怎么办?
两个duration时钟周期不同时,会先统一成一种时钟,然后再做加减运算。统一成同一种时钟的规则:
对于ratio<x1, y1> count1; ratio<x2, y2> count2; ,如果x1,x2最大公约数为x,y1,y2最小公倍数y,那么统一后的ratio为ratio<x, y>。
下面的例子,用2个duration做减法运算
#include <chrono>
#include <iostream>
void TestChrono()
{
std::chrono::duration<double, std::ratio<9, 7>> d1(3);
std::chrono::duration<double, std::ratio<6, 5>> d2(1);
auto d3 = d1 - d2;
std::cout << typeid(d3).name() << std::endl;
std::cout << d3.count() << std::endl;
}
在GCC下,将输出:
NSt6chrono8durationIdSt5ratioILl3ELl35EEEE
31
对于9/7,6/5,分子最大公约数3,分母最小公倍数35,统一后duration为std::chrono::duration<double, ratio<3, 35>>。因此d1 - d2 = (9/7)/(3/353) - (6/5)/(3/351) = 31。
duration_cast 转型
将当前的时钟周期转换为其他时钟周期。可以把秒的时钟周期转换为分钟的时钟周期,然后通过count来获取转换后的分钟时间间隔:
std::cout << std::chrono::duration_cast<chrono::minutes>(t3).count() << " minutes" << std::endl; // t3是前面的540秒
输出结果:
9 minutes
time point:表示时间点
time_point表示一个时间点,用来获取从它的clock纪元开始所经过的duration(如,可能是Epoch time 1970.1.1 00:00:00 +0000以来的时间间隔)和当前的时间。可以做一些时间的比较和算术运算,可以和ctime库结合起来显示时间。time_point必须用clock来计时。
计算到Epoch time的时间
time_point有一个函数time_since_epoch()用来获取从1970年1月1日到当前time_point时间点,所经过的duration。
下面是利用time_point计算当前时间距离1970年1月1日有多少天的示例:
#include <chrono>
#include <ratio>
#include <iostream>
void test_timepoint1()
{
using namespace std::chrono;
typedef duration<int, std::ratio<60 * 60 * 24>> days_type;
time_point<system_clock, days_type> today = time_point_cast<days_type>(system_clock::now());
std::cout << today.time_since_epoch().count() << " days since epoch" << std::endl; // 19140 days since epoch
}
time_point算术运算
time_point支持一些算术运算,如2个time_point差值表示时钟周期数,time_point与duration相加减表示另一个time_point。
注意:不同clock的time_point不能进行算术运算。
下面例子输出前一天和后一天的日期:
#include <chrono>
#include <iostream>
#include <iomanip>
#include <ctime>
void test_timepoint2()
{
using namespace std::chrono;
system_clock::time_point now = system_clock::now();
time_t last = system_clock::to_time_t(now - hours(24));
time_t next = system_clock::to_time_t(now + hours(24));
std::cout << "One day ago, the time was "
<< std::put_time(std::localtime(&last), "%F %T") << '\n'; // 格式命令同strftime(3)
std::cout << "Next day, the time is "
<< std::put_time(std::localtime(&next), "%F %T") << '\n';
}
运行结果:
One day ago, the time was 2022-05-27 15:28:13
Next day, the time is 2022-05-29 15:28:13
对于不支持std::put_time的编译器,可以用strftime,将tm结构对象转换为格式化后的时间字符串。
clocks 获取系统时钟
clocks表示当前系统时钟,内部有time_point, duration, Rep, Period等信息,主要用来获取当前时间,以及实现time_t和time_point的相互转换。clocks包含以下3种时钟:
- system_clock 代表真实世界的挂钟时间,具体时间值依赖于系统。system_clock保证提供的时间值是一个可读时间。
- steady_clock 不能被“调整”的时钟,并不一定代表真实世界的挂钟时间。保证先后调用now()得到的时间值是不会递减的。
- high_resolution_clock 高精度时钟,实际上是system_clock或者steady_clock别名。
system_clock 挂钟时间
可通过now()来获取当前时间点,然后用2次now()获取的time_point做差值运算,可以得到这段时间对应的时钟周期数。
如果想要打印出指定单位的时间,可以用duration_cast<>对这个差值进行转型,得到指定时钟周期的duration:
#include <chrono>
#include <iostream>
void test_clocks()
{
std::chrono::system_clock::time_point t1 = std::chrono::system_clock::now(); // 默认时钟周期是1纳秒
std::cout << "Hello World\n";
std::chrono::system_clock::time_point t2 = std::chrono::system_clock::now();
std::cout << (t2 - t1).count() << " tick count" << std::endl;
std::cout << std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count() << " microseconds" << std::endl;
}
这种方法常用于计算一段程序或一个算法的实际耗费时间。注意:这里获取的时间是读取2个点的当前系统时钟,然后算术转换而来,也就是说包含线程阻塞时间。
运行结果:
38500 tick count
38 microseconds
system_clock类型转换
1)time_point转换为time_t
成员方法to_time_t,将time_point类型对象转换为time_t。
2)time_t转换为time_point
成员方法from_time_t,将time_t类型对象转换为time_point。
time_point与time_t相互转换示例:
#include <chrono>
#include <iostream>
#include <ctime>
void test_clocks2()
{
using namespace std::chrono;
system_clock::time_point now = system_clock::now();
time_t last = system_clock::to_time_t(now - hours(24)); // time point: 24 hours before now time
system_clock::time_point lastday_point = system_clock::from_time_t(last);
std::cout << duration_cast<hours>(now - lastday_point).count() << "hours" << std::endl; // 打印 "24hours"
}
system_clock格式化日期输出
system_clock和std::put_time配合使用,可以格式化日期的输出。下面例子将当前时间格式化输出:
#include <chrono>
#include <iostream>
#include <iomanip>
#include <ctime>
void test_system_clock()
{
using namespace std;
auto t = chrono::system_clock::to_time_t(chrono::system_clock::now());
cout << std::put_time(std::localtime(&t), "%Y-%m-%d %X") << endl;
cout << std::put_time(std::localtime(&t), "%Y-%m-%d %H.%M.%S") << endl;
}
运行结果:
2022-05-28 16:24:54
2022-05-28 16.24.54
steady_clock 不能被调整的时钟
steady_clock可获得稳定可靠的时间间隔,后一次调用now()的值和前一次的差值不会因为修改了系统时间而改变,从而保证了稳定的时间间隔。steady_clock用法和system_clock一样。
自定义计时器timer
利用high_resolution_clock实现一个类似于boost.timer的计时器timer,在测试性能时会经常用到。timer常用于测试函数耗时,基本用法:
void fun()
{
cout << "Hello world" << endl;
}
int main()
{
Timer t;
fun();
cout << t.elapsed() << endl; // 打印func函数耗时(毫秒数)
return 0;
}
注意:high_resolution_clock是system_clock或steady_clock别名。
// g++ 9.4.0
using high_resolution_clock = system_clock;
下面利用C++11的chrono库来实现该定时器timer。
#include <chrono>
using namespace std;
using namespace std::chrono;
class Timer
{
public:
Timer(): begin_(high_resolution_clock::now()) {}
void reset() { begin_ = high_resolution_clock::now(); }
// 默认输出毫秒
template<typename Duration=milliseconds>
int64_t elapsed() const
{
return duration_cast<Duration>(high_resolution_clock::now() - begin_).count();
}
// 微秒
int64_t elapsed_micro() const
{
return elapsed<microseconds>();
}
// 纳秒
int64_t elapsed_nano() const
{
return elapsed<nanoseconds>();
}
// 秒
int64_t elapsed_seconds() const
{
return elapsed<seconds>();
}
// 分
int64_t elapsed_minutes() const
{
return elapsed<minutes>();
}
// 时
int64_t elapsed_hours() const
{
return elapsed<hours>();
}
private:
high_resolution_clock::time_point begin_;
};
void fun()
{
cout << "Hello world" << endl;
}
int main()
{
Timer t; // 定义定时器对象时, 开始计时
fun();
cout << t.elapsed() << endl; // 打印fun函数耗费毫秒数
cout << t.elapsed_nano() << endl; // 打印纳秒
cout << t.elapsed_micro() << endl; // 打印微秒
cout << t.elapsed_seconds() << endl; // 打印秒
cout << t.elapsed_minutes() << endl; // 打印分
cout << t.elapsed_hours() << endl; // 打印时
return 0;
}
运行结果:
Hello world
0
394800
577
0
0
0
总结
- chrono提供duration、time_point很方便获取时间间隔和时间点,并支持做简单算术运算,还可以配合一些辅助方法(如put_time)输出格式化时间。
- 获取系统时钟2种方式:system_clock获取真实世界挂钟时间,包含线程阻塞时间,但该时钟可以被人为修改;steady_clock获取时钟无法被人为修改,先后调用now()得到的时间绝不会递减,该时间也包含线程阻塞时间。
参考
[1]祁宇. 深入应用C++ 11:代码优化与工程级应用[M]. 机械工业出版社, 2015.