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.

posted @ 2022-05-28 16:51  明明1109  阅读(3912)  评论(0编辑  收藏  举报