• 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.

  • 相关阅读:
    Window7幻灯片字体显示混乱,难道真的是病毒么
    COCOS2DX 3.0 优化提升渲染速度 Auto-batching
    iOS 打印出视图中全部的子视图的名称
    【linux】学习2
    【编程之美】2.16 求数组的最大递增子序列
    【linux】学习1
    【编程之美】2.15 子数组之和的最大值(二维)
    【编程之美】2.14 求数组的子数组之和的最大值
    【QT】视频播放
    【编程之美】3.5 最短摘要的生成
  • 原文地址:https://www.cnblogs.com/fortunely/p/16321080.html
Copyright © 2020-2023  润新知