C++11提供chrono库,可以很方便地用于处理日期和时间。
chrono库主要包含3种类型:时间间隔duration、时钟clocks、时间点time point。
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。
将当前的时钟周期转换为其他时钟周期。可以把秒的时钟周期转换为分钟的时钟周期,然后通过count来获取转换后的分钟时间间隔:
std::cout << std::chrono::duration_cast<chrono::minutes>(t3).count() << " minutes" << std::endl; // t3是前面的540秒
输出结果:
9 minutes
time_point表示一个时间点,用来获取从它的clock纪元开始所经过的duration(如,可能是Epoch time 1970.1.1 00:00:00 +0000以来的时间间隔)和当前的时间。可以做一些时间的比较和算术运算,可以和ctime库结合起来显示时间。time_point必须用clock来计时。
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支持一些算术运算,如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表示当前系统时钟,内部有time_point, duration, Rep, Period等信息,主要用来获取当前时间,以及实现time_t和time_point的相互转换。clocks包含以下3种时钟:
可通过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
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和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可获得稳定可靠的时间间隔,后一次调用now()的值和前一次的差值不会因为修改了系统时间而改变,从而保证了稳定的时间间隔。steady_clock用法和system_clock一样。
利用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
[1]祁宇. 深入应用C++ 11:代码优化与工程级应用[M]. 机械工业出版社, 2015.