请选择 进入手机版 | 继续访问电脑版

[C 语言] C++使用 chrono库处理日期和时间的实现方法

  [复制链接]
查看222 | 回复57 | 2021-9-12 19:21:18 | 显示全部楼层 |阅读模式
目次

C++11 中提供了日期和时间相干 的库 chrono,通过 chrono 库可以很方便地处理日期和时间,为程序的开辟 提供了便利。chrono 库重要 包含三种范例 的类:时间间隔duration、时钟clocks、时间点time point。

1. 时间间隔 duration

1.1 常用类成员

duration表示一段时间间隔,用来记录时间长度,可以表示几秒、几分钟、几个小时的时间间隔。duration 的原型如下:

  1. // 定义于头文件 <chrono>
  2. template<
  3. class Rep,
  4. class Period = std::ratio<1>
  5. > class duration;
复制代码

ratio 类表示每个时钟周期的秒数,此中 第一个模板参数 Num 代表分子,Denom 代表分母,该分母值默以为 1,因此,ratio 代表的是一个分子除以分母的数值,比如:ratio<2> 代表一个时钟周期是 2 秒,ratio<60 > 代表一分钟,ratio<60*60 > 代表一个小时,ratio<60*60*24 > 代表一天。而 ratio<1,1000 > 代表的是 1/1000 秒,也就是 1 毫秒,ratio<1,1000000 > 代表一微秒,ratio<1,1000000000 > 代表一纳秒。

为了方便利用 ,在标准库中定义了一些常用的时间间隔,比如:时、分、秒、毫秒、微秒、纳秒,它们都位于 chrono 定名 空间下,定义如下:

范例 定义
纳秒:std::chrono::nanoseconds duration
微秒:std::chrono::microseconds duration
毫秒:std::chrono::milliseconds duration
秒:std::chrono::seconds duration
分钟:std::chrono::minutes duration>
小时:std::chrono::hours duration>

留意 :到 hours 为止的每个预定义时长范例 至少涵盖 ±292 年的范围。

duration 类的构造函数原型如下:

  1. // 1. 拷贝构造函数
  2. duration( const duration& ) = default;
  3. // 2. 通过指定时钟周期的类型来构造对象
  4. template< class Rep2 >
  5. constexpr explicit duration( const Rep2& r );
  6. // 3. 通过指定时钟周期类型,和时钟周期长度来构造对象
  7. template< class Rep2, class Period2 >
  8. constexpr duration( const duration<Rep2,Period2>& d );
复制代码

为了更加方便的举行 duration 对象之间的操作,类内部举行 了操作符重载:

操作符重载 形貌
operator= 赋值内容 (公开成员函数)
operator+ operator- 赋值内容 (公开成员函数)
operator++ operator++(int) operator– operator–(int) 递增或递减周期计数 (公开成员函数)
operator+= operator-= operator*= operator/= operator%= 实现二个时长间的复合赋值 (公开成员函数)

duration 类还提供了获取时间间隔的时钟周期数的方法 count (),函数原型如下:

  1. constexpr rep count() const;
复制代码

1.2 类的利用

通过构造函数构造变乱 间隔对象示例代码如下:

  1. #include <chrono>
  2. #include <iostream>
  3. using namespace std;
  4. int main()
  5. {
  6. chrono::hours h(1); // 一小时
  7. chrono::milliseconds ms{ 3 }; // 3 毫秒
  8. chrono::duration<int, ratio<1000>> ks(3); // 3000 秒
  9. // chrono::duration<int, ratio<1000>> d3(3.5); // error
  10. chrono::duration<double> dd(6.6); // 6.6 秒
  11. // 使用小数表示时钟周期的次数
  12. chrono::duration<double, std::ratio<1, 30>> hz(3.5);
  13. }
复制代码
  • h(1) 时钟周期为 1 小时,共有 1 个时钟周期,以是 h 表示的时间间隔为 1 小时
  • ms(3) 时钟周期为 1 毫秒,共有 3 个时钟周期,以是 ms 表示的时间间隔为 3 毫秒
  • ks(3) 时钟周期为 1000 秒,一共有三个时钟周期,以是 ks 表示的时间间隔为 3000 秒
  • d3(3.5) 时钟周期为 1000 秒,时钟周期数量 只能用整形来表示,但是此处指定的是浮点数,因此语法错误
  • dd(6.6) 时钟周期为默认的 1 秒,共有 6.6 个时钟周期,以是 dd 表示的时间间隔为 6.6 秒
  • hz(3.5) 时钟周期为 1/30 秒,共有 3.5 个时钟周期,以是 hz 表示的时间间隔为 1/30*3.5 秒

chrono 库中根据 duration 类封装了不同长度的时钟周期(也可以自定义),基于这个时钟周期再举行 周期次数的设置就可以得到总的时间间隔了(时钟周期 * 周期次数 = 总的时间间隔)。

示例代码如下:

  1. #include <chrono>
  2. #include <iostream>
  3. int main()
  4. {
  5. std::chrono::milliseconds ms{3}; // 3 毫秒
  6. std::chrono::microseconds us = 2*ms; // 6000 微秒
  7. // 时间间隔周期为 1/30 秒
  8. std::chrono::duration<double, std::ratio<1, 30>> hz(3.5);
  9. std::cout << "3 ms duration has " << ms.count() << " ticks\n"
  10. << "6000 us duration has " << us.count() << " ticks\n"
  11. << "3.5 hz duration has " << hz.count() << " ticks\n";
  12. }
复制代码

输出的结果 为:

  1. 3 ms duration has 3 ticks
  2. 6000 us duration has 6000 ticks
  3. 3.5 hz duration has 3.5 ticks
复制代码
  • ms 时间单位为毫秒,初始化操作 ms{3} 表示时间间隔为 3 毫秒,一共有 3 个时间周期,每个周期为 1 毫秒
  • us 时间单位为微秒,初始化操作 2*ms 表示时间间隔为 6000 微秒,一共有 6000 个时间周期,每个周期为 1 微秒
  • hz 时间单位为秒,初始化操作 hz(3.5) 表示时间间隔为 1/30*3.5 秒,一共有 3.5 个时间周期,每个周期为 1/30 秒

由于在 duration 类内部做了操作符重载,因此时间间隔之间可以直接举行 算术运算,比如我们要计算两个时间间隔的差值,就可以在代码中做如下处理:

  1. #include <iostream>
  2. #include <chrono>
  3. using namespace std;
  4. int main()
  5. {
  6. chrono::minutes t1(10);
  7. chrono::seconds t2(60);
  8. chrono::seconds t3 = t1 - t2;
  9. cout << t3.count() << " second" << endl;
  10. }
复制代码

程序输出的结果 :

  1. 540 second
复制代码

在上面的测试程序中,t1 代表 10 分钟,t2 代表 60 秒,t3 是 t1 减去 t2,也就是 60*10-60=540,这个 540 表示的时钟周期,每个时钟周期是 1 秒,因此两个时间间隔之间的差值为 540 秒。

留意 事项:duration 的加减运算有肯定 的规则,当两个 duration 时钟周期不类似 的时间 ,会先同一 成一种时钟,然后再举行 算术运算,同一 的规则如下:假设有 ratio 和 ratio 两个时钟周期,起首 必要 求出 x1,x2 的最大公约数 X,然后求出 y1,y2 的最小公倍数 Y,同一 之后的时钟周期 ratio 为 ratio

  1. #include <iostream>
  2. #include <chrono>
  3. using namespace std;
  4. int main()
  5. {
  6. chrono::duration<double, ratio<9, 7>> d1(3);
  7. chrono::duration<double, ratio<6, 5>> d2(1);
  8. // d1 和 d2 统一之后的时钟周期
  9. chrono::duration<double, ratio<3, 35>> d3 = d1 - d2;
  10. }
复制代码

对于分子 6,、9 最大公约数为 3,对于分母 7、5 最小公倍数为 35,因此推导出的时钟周期为 ratio<3,35>

2. 时间点 time point

chrono 库中提供了一个表示时间点的类 time_point,该类的定义如下:

  1. // 定义于头文件 <chrono>
  2. template<
  3. class Clock,
  4. class Duration = typename Clock::duration
  5. > class time_point;
复制代码

它被实现成犹如 存储一个 Duration 范例 的自 Clock 的纪元起始开始的时间间隔的值,通过这个类终极 可以得到时间中的某一个时间点。

  • Clock:此时间点在此时钟上计量
  • Duration:用于计量从纪元起时间的 std::chrono::duration 范例

time_point 类的构造函数原型如下:

  1. // 1. 构造一个以新纪元(epoch,即:1970.1.1)作为值的对象,需要和时钟类一起使用,不能单独使用该无参构造函数
  2. time_point();
  3. // 2. 构造一个对象,表示一个时间点,其中d的持续时间从epoch开始,需要和时钟类一起使用,不能单独使用该构造函数
  4. explicit time_point( const duration& d );
  5. // 3. 拷贝构造函数,构造与t相同时间点的对象,使用的时候需要指定模板参数
  6. template< class Duration2 >
  7. time_point( const time_point<Clock,Duration2>& t );
复制代码

在这个类中除了构造函数还提供了别的 一个 time_since_epoch() 函数,用来获得 1970 年 1 月 1 日到 time_point 对象中记录的时间颠末 的时间间隔(duration),函数原型如下:

  1. duration time_since_epoch() const;
复制代码

除此之外,时间点 time_point 对象和时间段对象 duration 之间还支持直接举行 算术运算(即加减运算),时间点对象之间可以举行 逻辑运算,详细 细节可以参考下面的表格:

此中 tp 和 tp2 是 time_point 范例 的对象, dtn 是 duration 范例 的对象。

形貌 操作 返回值
复合赋值 (成员函数) operator+= tp += dtn *this
复合赋值 (成员函数)  operator-= tp -= dtn *this
算术运算符 (非成员函数) operator+ tp + dtn a time_point value
算术运算符 (非成员函数) operator+ dtn + tp a time_point value
算术运算符 (非成员函数) operator- tp - dtn a time_point value
算术运算符 (非成员函数) operator- ttp - tp2 aduration value
关系操作符 (非成员函数) operator== tp == tp2 a bool value
关系操作符 (非成员函数) operator!= tp != tp2 a bool value
关系操作符 (非成员函数) operator< tp < tp2 a bool value
关系操作符 (非成员函数) operator> tp > tp2 a bool value
关系操作符 (非成员函数) operator>= tp >= tp2 a bool value
关系操作符 (非成员函数) operator<= tp <= tp2 a bool value

由于该时间点类常常 和下面要先容 的时钟类一起利用 ,以是 在此先不举例,在时钟类的示例代码中会涉及到时间点类的利用 ,到此为止只必要 搞明确 时间点类的提供的这几个函数的作用就可以了。

3. 时钟 clocks

chrono 库中提供了获取当前的体系 时间的时钟类,包含的时钟一共有三种:

  • system_clock:体系 的时钟,体系 的时钟可以修改,以致 可以网络对时,因此利用 体系 时间计算时间差大概 不准。
  • steady_clock:是固定的时钟,相当 于秒表。开始计时后,时间只会增长并且不能修改,得当 用于记录程序耗时
  • high_resolution_clock:和时钟类 steady_clock 是等价的(是它的别名)。

在这些时钟类的内部有 time_point、duration、Rep、Period 等信息,基于这些信息来获取当前时间,以及实现 time_t 和 time_point 之间的相互转换。

时钟类成员范例 形貌
rep 表示时钟周期次数的有符号算术范例
period 表示时钟计次周期的 std::ratio 范例
duration 时间间隔,可以表示负时长
time_point 表示在当前时钟里边记录的时间点

在利用 chrono提供的时钟类的时间 ,不必要 创建类对象,直接调用类的静态方法就可以得到想要的时间了。

3.1 system_clock

详细 来说,时钟类 system_clock 是一个体系 范围的及时 时钟。system_clock 提供了对当前时间点 time_point 的访问,将得到时间点转换为 time_t 范例 的时间对象,就可以基于这个时间对象获取到当前的时间信息了。

system_clock 时钟类在底层源码中的定义如下:

  1. struct system_clock { // wraps GetSystemTimePreciseAsFileTime/GetSystemTimeAsFileTime
  2. using rep = long long;
  3. using period = ratio<1, 10'000'000>; // 100 nanoseconds
  4. using duration = chrono::duration<rep, period>;
  5. using time_point = chrono::time_point<system_clock>;
  6. static constexpr bool is_steady = false;
  7. _NODISCARD static time_point now() noexcept
  8. { // get current time
  9. return time_point(duration(_Xtime_get_ticks()));
  10. }
  11. _NODISCARD static __time64_t to_time_t(const time_point& _Time) noexcept
  12. { // convert to __time64_t
  13. return duration_cast<seconds>(_Time.time_since_epoch()).count();
  14. }
  15. _NODISCARD static time_point from_time_t(__time64_t _Tm) noexcept
  16. { // convert from __time64_t
  17. return time_point{seconds{_Tm}};
  18. }
  19. };
复制代码

通过以上源码可以相识 到在 system_clock 类中的一些细节信息:

  • rep:时钟周期次数是通过整形来记录的 long long
  • period:一个时钟周期是 100 纳秒 ratio<1, 10'000'000>
  • duration:时间间隔为 rep*period 纳秒 chrono::duration
  • time_point:时间点通过体系 时钟做了初始化 chrono::time_p- oint,内里 记录了新纪元时间点

别的 还可以看到 system_clock 类一共提供了三个静态成员函数:

  1. // 返回表示当前时间的时间点。
  2. static std::chrono::time_point<std::chrono::system_clock> now() noexcept;
  3. // 将 time_point 时间点类型转换为 std::time_t 类型
  4. static std::time_t to_time_t( const time_point& t ) noexcept;
  5. // 将 std::time_t 类型转换为 time_point 时间点类型
  6. static std::chrono::system_clock::time_point from_time_t( std::time_t t ) noexcept;
复制代码

比如,我们要获取当前的体系 时间,并且必要 将其以可以或许 辨认 的方式打印出来,示例代码如下:

  1. #include <chrono>
  2. #include <iostream>
  3. using namespace std;
  4. using namespace std::chrono;
  5. int main()
  6. {
  7. // 新纪元1970.1.1时间
  8. system_clock::time_point epoch;
  9. duration<int, ratio<60*60*24>> day(1);
  10. // 新纪元1970.1.1时间 + 1天
  11. system_clock::time_point ppt(day);
  12. using dday = duration<int, ratio<60 * 60 * 24>>;
  13. // 新纪元1970.1.1时间 + 10天
  14. time_point<system_clock, dday> t(dday(10));
  15. // 系统当前时间
  16. system_clock::time_point today = system_clock::now();
  17. // 转换为time_t时间类型
  18. time_t tm = system_clock::to_time_t(today);
  19. cout << "今天的日期是: " << ctime(&tm);
  20. time_t tm1 = system_clock::to_time_t(today+day);
  21. cout << "明天的日期是: " << ctime(&tm1);
  22. time_t tm2 = system_clock::to_time_t(epoch);
  23. cout << "新纪元时间: " << ctime(&tm2);
  24. time_t tm3 = system_clock::to_time_t(ppt);
  25. cout << "新纪元时间+1天: " << ctime(&tm3);
  26. time_t tm4 = system_clock::to_time_t(t);
  27. cout << "新纪元时间+10天: " << ctime(&tm4);
  28. }
复制代码

示例代码打印的结果 为:

  1. 今天的日期是:    Thu Apr  8 11:09:49 2021
  2. 明天的日期是:    Fri Apr  9 11:09:49 2021
  3. 新纪元时间:      Thu Jan  1 08:00:00 1970
  4. 新纪元时间+1天:  Fri Jan  2 08:00:00 1970
  5. 新纪元时间+10天: Sun Jan 11 08:00:00 1970
复制代码

3.2 steady_clock

假如 我们通过时钟不是为了获取当前的体系 时间,而是举行 程序耗时的时长,此时利用 syetem_clock 就不合适了,由于 这个时间可以跟随体系 的设置发生变化。在 C++11 中提供的时钟类 steady_clock 相当 于秒表,只要启动就会举行 时间的累加,并且不能被修改,非常得当 于举行 耗时的统计。

steady_clock 时钟类在底层源码中的定义如下:

  1. struct steady_clock { // wraps QueryPerformanceCounter
  2. using rep = long long;
  3. using period = nano;
  4. using duration = nanoseconds;
  5. using time_point = chrono::time_point<steady_clock>;
  6. static constexpr bool is_steady = true;
  7. // get current time
  8. _NODISCARD static time_point now() noexcept
  9. {
  10. // doesn't change after system boot
  11. const long long _Freq = _Query_perf_frequency();
  12. const long long _Ctr = _Query_perf_counter();
  13. static_assert(period::num == 1, "This assumes period::num == 1.");
  14. const long long _Whole = (_Ctr / _Freq) * period::den;
  15. const long long _Part = (_Ctr % _Freq) * period::den / _Freq;
  16. return time_point(duration(_Whole + _Part));
  17. }
  18. };
复制代码

通过以上源码可以相识 到在 steady_clock 类中的一些细节信息:

  • rep:时钟周期次数是通过整形来记录的 long long
  • period:一个时钟周期是 1 纳秒 nano
  • duration:时间间隔为 1 纳秒 nanoseconds
  • time_point:时间点通过体系 时钟做了初始化 chrono::time_point

别的 ,在这个类中也提供了一个静态的 now () 方法,用于得到当前的时间点,函数原型如下:

  1. static std::chrono::time_point<std::chrono::steady_clock> now() noexcept;
复制代码

假设要测试某一段程序的实行 服从 ,可以计算它实行 期间斲丧 的总时长,示例代码如下:

  1. #include <chrono>
  2. #include <iostream>
  3. using namespace std;
  4. using namespace std::chrono;
  5. int main()
  6. {
  7. // 获取开始时间点
  8. steady_clock::time_point start = steady_clock::now();
  9. // 执行业务流程
  10. cout << "print 1000 stars ...." << endl;
  11. for (int i = 0; i < 1000; ++i)
  12. {
  13. cout << "*";
  14. }
  15. cout << endl;
  16. // 获取结束时间点
  17. steady_clock::time_point last = steady_clock::now();
  18. // 计算差值
  19. auto dt = last - start;
  20. cout << "总共耗时: " << dt.count() << "纳秒" << endl;
  21. }
复制代码

3.3 high_resolution_clock

high_resolution_clock 提供的时钟精度比 system_clock 要高,它也是不可以修改的。在底层源码中,这个类着实 是 steady_clock 类的别名。

using high_resolution_clock = steady_clock;
因此 high_resolution_clock 的利用 方式和 steady_clock 是一样的,在此就不再过多举行 赘述了。

4. 转换函数

4.1 duration_cast

duration_cast 是 chrono 库提供的一个模板函数,这个函数不属于 duration 类。通过这个函数可以对 duration 类对象内部的时钟周期 Period,和周期次数的范例 Rep 举行 修改,该函数原型如下:

  1. template <class ToDuration, class Rep, class Period>
  2. constexpr ToDuration duration_cast (const duration<Rep,Period>& dtn);
复制代码

在源周期能正确 地为目的 周期所整除的场合(比方 小时到分钟),浮点时长和整数时长间转型能隐式举行 无需利用 duration_cast ,其他环境 下都必要 通过函数举行 转换。

我们可以修改一下上面测试程序实行 时间的代码,在代码中修改 duration 对象的属性:

  1. #include <iostream>
  2. #include <chrono>
  3. using namespace std;
  4. using namespace std::chrono;
  5. void f()
  6. {
  7. cout << "print 1000 stars ...." << endl;
  8. for (int i = 0; i < 1000; ++i)
  9. {
  10. cout << "*";
  11. }
  12. cout << endl;
  13. }
  14. int main()
  15. {
  16. auto t1 = steady_clock::now();
  17. f();
  18. auto t2 = steady_clock::now();
  19. // 整数时长:要求 duration_cast
  20. auto int_ms = duration_cast<chrono::milliseconds>(t2 - t1);
  21. // 小数时长:不要求 duration_cast
  22. duration<double, ratio<1, 1000>> fp_ms = t2 - t1;
  23. cout << "f() took " << fp_ms.count() << " ms, "
  24. << "or " << int_ms.count() << " whole milliseconds\n";
  25. }
复制代码

示例代码输出的结果 :

  1. print 1000 stars ....
  2. ****************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************
  3. f() took 40.2547 ms, or 40 whole milliseconds
复制代码

4.2 time_point_cast

time_point_cast 也是 chrono 库提供的一个模板函数,这个函数不属于 time_point 类。函数的作用是对时间点举行 转换,由于 不同的时间点对象内部的时钟周期 Period,和周期次数的范例 Rep 大概 也是不同的,一样平常 环境 下它们之间可以举行 隐式范例 转换,也可以通过该函数表现 的举行 转换,函数原型如下:

  1. template <class ToDuration, class Clock, class Duration>
  2. time_point<Clock, ToDuration> time_point_cast(const time_point<Clock, Duration> &t);
复制代码

关于函数的利用 ,示例代码如下:

  1. #include <chrono>
  2. #include <iostream>
  3. using namespace std;
  4. using Clock = chrono::high_resolution_clock;
  5. using Ms = chrono::milliseconds;
  6. using Sec = chrono::seconds;
  7. template<class Duration>
  8. using TimePoint = chrono::time_point<Clock, Duration>;
  9. void print_ms(const TimePoint<Ms>& time_point)
  10. {
  11. std::cout << time_point.time_since_epoch().count() << " ms\n";
  12. }
  13. int main()
  14. {
  15. TimePoint<Sec> time_point_sec(Sec(6));
  16. // 无精度损失, 可以进行隐式类型转换
  17. TimePoint<Ms> time_point_ms(time_point_sec);
  18. print_ms(time_point_ms); // 6000 ms
  19. time_point_ms = TimePoint<Ms>(Ms(6789));
  20. // error,会损失精度,不允许进行隐式的类型转换
  21. TimePoint<Sec> sec(time_point_ms);
  22. // 显示类型转换,会损失精度。6789 truncated to 6000
  23. time_point_sec = std::chrono::time_point_cast<Sec>(time_point_ms);
  24. print_ms(time_point_sec); // 6000 ms
  25. }
复制代码

留意 事项:关于时间点的转换假如 没有没有精度的丧失 可以直接举行 隐式范例 转换,假如 会丧失 精度只能通过表现 范例 转换,也就是调用 time_point_cast 函数来完成该操作。

到此这篇关于C++利用 chrono库处理日期和时间的实现方法的文章就先容 到这了,更多相干 C++ chrono日期和时间处理内容请搜刮 脚本之家从前 的文章或继续欣赏 下面的相干 文章盼望 大家以后多多支持脚本之家!


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

avatar 回忆还是面包傲 | 2021-9-12 21:13:52 | 显示全部楼层
帖子好乱!
回复

使用道具 举报

avatar Vonice | 2021-9-13 17:01:48 | 显示全部楼层
admin楼主会死的很有节奏的!
回复

使用道具 举报

avatar 珍惜637 | 2021-9-15 22:19:25 | 显示全部楼层
admin楼主很有经验啊!
回复

使用道具 举报

avatar yslzaity | 2021-9-19 06:45:31 | 显示全部楼层
什么狗屁帖子啊,admin楼主的语文是苍老师教的吗?
回复

使用道具 举报

avatar 尹以为荣 | 2021-9-19 23:23:46 | 显示全部楼层
大神好强大!
回复

使用道具 举报

avatar 旭日非常 | 2021-9-20 09:34:55 | 显示全部楼层
看了这么多帖子,第一次看到这么经典的!
回复

使用道具 举报

avatar 封号955 | 2021-9-24 14:46:59 | 显示全部楼层
哥回复的不是帖子,是寂寞!
回复

使用道具 举报

avatar 廊桥遗梦504 | 2021-9-24 14:47:02 | 显示全部楼层
admin楼主又闹绯闻了!
回复

使用道具 举报

avatar 武汉嘉瑞 | 2021-9-30 08:28:43 | 显示全部楼层
赞一个!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则