跳转到内容

C++/STL/chrono

维基教科书,自由的教学读本
< C++

程序设计语言中,chrono标准模板库(STL)中与时间有关的头文件(自C++11开始)。该头文件中所有函数与类模板均定义在std::chrono命名空间中。

概念[编辑]

  • duration:时间长度,如1分钟、2小时、10毫秒等。表示为类模板duration的对象,用一个count representation与一个period precision表示。例如,10毫秒的10为count representation,毫秒为period precision。
  • time points:表示一个时间点。例如某人的生日、今天的日出时间等。表示为类模板time_point的对象。用相对于一个固定时间点epoch的duration来表示。
  • clocks:时间点相对于真实物理时间的框架。C++11提供了3个clock,C++20还提供了4个clock:
    • system_clock:可被管理员手工调整
    • steady_clock:不受管理员人为影响,适用于时间流逝的计量
    • high_resolution_clock
    • utc_clock — UTC时间,包括闰秒
    • tai_clock — 国际原子时间,不含闰秒
    • gps_clock — GPS时间,不含闰秒
    • file_clock — 文件系统时间

类模板[编辑]

时间长度duration[编辑]

表示time span。该类模板声明为:

 template <class Rep, class Period=ratio<1> > class duration;

第一个模板参数为存储时间计数的数据类型,如整型、浮点型等。成员函数count()返回该计数。第二个模板参数表示计数的一个周期,一般是std::ratio(即有理数)类型,表示一个周期(即一个时间嘀嗒tick)是秒钟的倍数或分数,在编译时应为一个有理数常量。

duration模板类实例化typedef:

类型 Rep Period
years signed integral至少17比特 std::ratio<86400>
months signed integral至少20比特 ratio<2629746,1>
weeks signed integral至少22比特 ratio<604800,1>
days signed integral至少25比特 ratio<86400,1>
hours signed integral至少23比特 ratio<3600,1>
minutes signed integral至少29比特 ratio<60,1>
seconds signed integral至少35比特 ratio<1,1>
miliseconds signed integral至少45比特 ratio<1,1000>
microseconds signed integral至少55比特 ratio<1,1000000>
nanoseconds signed integral至少64比特 ratio<1,1000000000>
rep 对应于模板的第1个参数
period 对应于模板的第2个参数

chrono提供的预定义的时间长度单位都是int64类型的内部存储数据。

  • duration模板类成员函数:
    • 构造函数
    • 析构函数
    • count 返回时间嘀嗒计数
    • zero: 静态函数,返回0值实例
    • min:静态函数,表示最小可能值。例如std::cout << (seconds::min)();
    • max:静态函数,表示最大可能值
    • operator= 赋值运算符
    • operator+ 酉运算符
    • operator- 酉运算符
    • operator++ 前缀++
    • operator++(int) 后缀++
    • operator-- 前缀--
    • operator--(int) 后缀--
    • operator+=
    • operator-=
    • operator*=
    • operator/=
    • operator%=
  • 非成员函数
    • std::common_type<std::chrono::duration> 是类模板std::common_type的特化版本。给出两个duration类的共同类型,即分母是最小公倍数。
    • operator+
    • operator-
    • operator*
    • operator/
    • operator%
    • operator==
    • operator!=
    • operator<
    • operator<=
    • operator>
    • operator>=
    • duration_cast 把时间长度转换到不同时间嘀嗒单位的另一个时间长度。存在截断误差时才需要这种显式类型转换。
    • floor(std::chrono::duration) C++17 转换时间长度向下近似
    • ceil(std::chrono::duration) C++17 转换时间长度向上近似
    • round(std::chrono::duration) C++17 转换时间长度向最近偶数近似
    • abs(std::chrono::duration) C++17 时间长度的绝对值
  • 帮助类Helper classes
    • treat_as_floating_point 表示一个时间长度可转换为不同的时间嘀嗒单位
    • duration_values 构造给定类型的repr类型的零值zero、最小值min、最大值max 等3个constexpr函数。例如auto iiii = std::chrono::duration_values<int>::min; auto jj = iiii();

C++14标准定义的字面量[编辑]

主页面:w:用户定义字面量

均定义在std::literals::chrono_literals命名空间中。因此需要先声明using namespace std::chrono_literals;

  • operator""h
  • operator""min
  • operator""s
  • operator""ms
  • operator""us
  • operator""ns

未定义大于小时的字面常量。

初始化[编辑]

duration初始化的形式:

  • std::chrono::seconds v1{3}; //不允许用圆括号或者赋值号
  • std::chrono::seconds v2{4min}; //相当于拷贝赋值

例子[编辑]

为了区分秒数和普通的数值之间的差异,seconds类在设计的时候就禁止了数值隐式转换为seconds类

seconds s = 3;  // 编译出错
seconds s1{3};  // 这样是可以的
s1 = 4; // 编译出错

std::cout << s << "\n"; // C++20可以,输出为 3s
std::cout << s.count() << "s\n"; // 这样是可以的

seconds s2 = 3s;    // C++14支持字面常量
seconds s3 = s2 + s1;   // 支持基本的算术操作,但是不支持seconds和一个普通数值进行算术操作

seconds::min(); // 获取秒类型可以表示的范围,
seconds::max();

minutes m1{2};
seconds s2 = m1;        // m1隐式转换为seconds
seconds s3 = s2 + m1;   // m1隐式转换为seconds,然后运算 

//不同时间长度单位的隐式转换必须是向下转换,如minutes转seconds。如果向上转换需用duration_cast显式转换。因为这些转换是截断近似,而不是四舍五入。
seconds s2{1000};
minutes m3 = std::chrono::duration_cast<minutes>(s2);

时间点time_point[编辑]

表示一个时间点,即一个时刻的值。它存储为从一个固定时刻(epoch)开始的一段时间的长度(duration)。C++20规定system clock采用的是Unix的时间戳即UTC 1970年1月1日 00:00。时间点的类模板形如:

template <class Clock,
          class Duration = typename Clock::duration>
class time_point {
    Duration d_;
public:
    using clock    = Clock;
    using duration = Duration;
    // ...  
};
  • 类型定义
    • clock 使用的时钟
    • duration 一个std::chrono::duration类型,用于度量从固定时刻开始的时间的长度
    • rep 算术类型,表示时间长度的嘀嗒(tick)数
    • period 一个std::ratio类型,表示一个时间嘀嗒的单位
  • 成员函数
    • (constructor)
    • time_since_epoch 返回从固定时刻开始的时间长度。
    • operator+= 修改时间长度
    • operator-= 修改时间长度
    • min 静态成员函数,最小可能的时间长度
    • max 静态成员函数,最大可能的时间长度
  • 非成员函数
    • std::common_type<std::chrono::time_point> 是类模板std::common_type的特化版本
    • operator+ 修改时间长度
    • operator- 修改时间长度
    • operator== 比较时间点
    • operator!= 比较时间点
    • operator< 比较时间点
    • operator<= 比较时间点
    • operator> 比较时间点
    • operator>= 比较时间点
    • time_point_cast 函数模板,在同一时钟下把一个时间点转化为具有不同时钟滴答长度的另一个时间点。所以第一个模板参数是目标的时钟抵达长度类型。
    • clock_cast C++20引入,可以转换不同时钟下的时间点。例如utc_clock::time_point t = clock_cast<utc_clock>(file_clock::now());
    • floor(std::chrono::time_point) C++17 函数模板 转化时间点并向下近似
    • ceil(std::chrono::time_point) C++17 函数模板 转化时间点并向上近似
    • round(std::chrono::time_point) C++17 函数模板 转化时间点并近似到最近邻的偶数

不同类型的时间点之间也可以相互转换,可隐式的向下转换;如果向上转换就必须显式的使用time_point_cast:

 using namespace std::chrono;
  template <class D> using sys_time = time_point<system_clock, D>;
  sys_time<seconds> tp{5s};            // 5s
  sys_time<milliseconds> tp2 = tp;     // 5000ms  隐式的向下转换
  tp = time_point_cast<seconds>(tp2);  // 5s  向下转换,需要显示的使用time_point_cast

时钟clock[编辑]

C++11预定义3个时钟,形如:

struct some_clock
{
    using duration = chrono::duration<int64_t, microseconds>;
    using rep = duration::rep;
    using period = duration::period;
    using time_point = chrono::time_point<some_clock>; 
    static constexpr bool is_steady = false; 
     static time_point now() noexcept;
};

C++20之前,不同时钟的时间点无法用标准库的工具相互转换:

system_clock::time_point tp = system_clock::now();
steady_clock::time_point tp2 = tp;  // no viable conversion

C++20引入结构模板clock_time_conversion,可以转换不同时钟下的时间点。其()运算符可把一个时间点转化为目标时钟下的对应时间点。以system时钟或者utc时钟为转换中介。例如:

    auto sd = sys_days{ 2021y / July / 26 };
    auto time = clock_time_conversion<utc_clock, system_clock>{}(sd);
    std::cout << time << "\n";

Visual C++ 2015把steady_clock都用QueryPerformanceCounter实现;把high_resolution_clock定义为steady_clock的别名。

system_clock[编辑]

当前系统范围(即对各进程都一致)的一个实时的日历时钟(wall clock)。但是这个值并不一定是单调的,因为操作系统可能会修改时间,导致system_clock返回的时间不单调。

  • 成员类型:
    • rep 表示时钟滴答计数的算术类型
    • period 表示时钟滴答长度的std::ratio类型,以秒钟为单位长度
    • duration std::chrono::duration<rep, period> 微软实现为 duration<long long, ratio<1, 10'000'000>即100纳秒。
    • time_point std::chrono::time_point<std::chrono::high_resolution_clock> 。微软实现epoch为1970-01-01 00:00:00 UTC
  • 成员常量:
    • constexpr bool is_steady (public static member constant) 表示该时钟的每个时间嘀嗒单位是否为均匀(即长度相等) 。
  • 成员函数
    • now 静态函数 返回时钟的当前值,类型为std::chrono::time_point
    • to_time_t 静态函数 把系统时钟的时间点转化为C风格的std::time_t
    • from_time_t 静态函数 把C风格的std::time_t转化为系统时钟的时间点

C++20引入3个预定义的time_point特化类:

  • sys_time
  • sys_seconds
  • sys_days

steady_clock[编辑]

当前系统实现的一个稳定时钟。表示的是单调时钟,随着物理时间向前,这个时钟的时间点不会减少,最适合进行间隔的测量。

  • 成员类型:
    • rep 表示时钟滴答计数的算术类型
    • period 表示时钟滴答长度的std::ratio类型,以秒钟为单位长度
    • duration std::chrono::duration<rep, period> 微软实现为duration<long long, nano>
    • time_point std::chrono::time_point<std::chrono::high_resolution_clock>
  • 成员常量:
    • constexpr bool is_steady (public static member constant) 表示该时钟的每个时间嘀嗒单位是否为均匀(即长度相等) 。该值总为true
  • 成员函数
    • now 静态函数 返回时钟的当前值,类型为std::chrono::time_point

注意,steady_clock的epoch不能转化为system timepoint;steady_clock的时间点不能输出输入给stl的流。实质上这相当于steady_clock是一个计时器而不是时钟,两个时间点相减得到的duration是有意义的,不存在时间原点epoch。

high_resolution_clock[编辑]

当前系统实现的最高分辨率的时钟。微软实现该时钟为steady_clock的类型别名。

  • 成员类型:
    • rep 表示时钟滴答计数的算术类型
    • period 表示时钟滴答长度的std::ratio类型,以秒钟为单位长度
    • duration std::chrono::duration<rep, period>
    • time_point std::chrono::time_point<std::chrono::high_resolution_clock>
  • 成员常量:
    • constexpr bool is_steady (静态) 表示该时钟的每个时间嘀嗒单位是否为均匀(即长度相等)
  • 成员函数
    • now 静态函数 返回时钟的当前值,类型为std::chrono::time_point

utc_clock[编辑]

此时钟自1970-01-01 00:00:00 UTC开始计时。此时钟计算闰秒,是世界各地民用时间的基础。微软实现它是system_clock的别名类型

3个静态成员函数:

  • now
  • to_sys
  • from_sys

非成员函数:

  • get_leap_second_info

C++20引入2个预定义的time_point特化类:

  • utc_time
  • utc_seconds

tai_clock[编辑]

epoch为1958-01-01 00:00:00.0000000UTC。

duration微软实现为duration<long long, ratio<1, 10'000'000>,即100纳秒。

3个静态成员函数:

  • from_utc
  • now
  • to_utc

C++20引入2个预定义的time_point特化类:

  • tai_time
  • tai_seconds

gps_clock[编辑]

epoch为1980-01-06 00:00:00.0000000UTC

duration微软实现为duration<long long, ratio<1, 10'000'000>,即100纳秒。

3个静态成员函数:

  • from_utc
  • now
  • to_utc

C++20引入2个预定义的time_point特化类:

  • gps_time
  • gps_seconds

file_clock[编辑]

为std::filesystem::file_time_type的别名类型。不是steady时钟,即有闰秒插入。

epoch微软实现为1601-01-01 00:00:00.0000000UTC

duration微软实现为duration<long long, ratio<1, 10'000'000>,即100纳秒。

静态成员函数:

  • now
  • to_utc
  • from_utc
  • to_sys
  • from_sys

由于C++20要求编译器实现to_utc/from_utc或to_sys/from_sys中的一套,因此建议使用clock_cast,例如clock_cast<file_clock>(utc_clock::now())

C++20引入2个预定义的time_point特化类:file_time

local_t[编辑]

C++20引入,表示本地时间的伪时钟。用于std::chrono::time_point的构造函数的第一个参数,实际上表示还没有跟具体哪个时钟相结合的时间点。

以此特化了3个std::chrono::time_point模板实例类:

  • template<class Duration> using local_time = std::chrono::time_point<std::chrono::local_t, Duration>;
  • using local_seconds = local_time<std::chrono::seconds>;
  • using local_days = local_time<std::chrono::days>;

一天之内的时间[编辑]

C++20用类模板std::chrono::hh_mm_ss把时间滴答长度表示为小时、分钟、秒钟、小数秒。主要用于格式化。成员函数有is_negative、hours、minutes、seconds、subseconds等。例如: cout << std::chrono::hh_mm_ss{ chrono::milliseconds{1003} } << '\n';

对于std::chrono::hours,std::chrono包含函数is_am、is_pm、make12、make24用于判断是否为上午[0h, 11h],否则为下午;转换为12小时/24小时表示。

日期[编辑]

C++20在std::chrono中增加:

  • last_spec:标记类用于定义一个类实例last,可表示月度最后一天,月度最后一个星期几。
  • day 类,表示月度中的一天,范围为[1,31],但也可[0, 255].
  • month 类,表示月度。范围为[1,12],但也可[0, 255]。预定义了12个月的英文名字作为类的实例如January值为1。
  • year 类,表示年份。范围为 [-32767, 32767]
  • weekday 类,表示星期各天名称。范围为[0,6],但也可[0, 255]。预定义了7个星期内各天名字作为类的实例如Sunday值为0 .
    • 重载了运算符[],下标如果为整型值i,表示未指定的月度内的第i个星期几;如果下标值为std::chrono::last_spec,表示未指定月度内的最后一个星期几。
  • weekday_indexed 类,表示月度内的第几个星期几
  • weekday_last 类,表示月度内的最后一个星期几
  • month_day 类,表示指定月的第几天
  • month_day_last类,表示月度的最后一天
  • month_weekday类,表示指定月度的第几个星期几
  • month_weekday_last类,表示制定月度的最后一个星期几
  • year_month类,表示指定年度的指定月度
  • year_month_day类,表示指定的日期
  • year_month_day_last类,表示指定年月的最后一天
  • year_month_weekday类,表示指定年月的第几个星期几。
  • year_month_weekday_last类,表示指定年月的最后一个星期几
  • operator/ 运算符重载,为方便创建格里高利历日期
    • 创建完整日期,允许3种顺序:
      • year/month/day
      • month/day/year
      • day/month/year.
      • 其中允许day替换为:
        • std::chrono::last, 月度最后一天
        • weekday[i], 月度内第i个星期几
        • weekday[std::chrono::last], 月度内最后一个星期几
      • 纯整数可被接手,如果不引起歧义,如2005y/4/5可以,但5/April/2005不行。
    • 部分日期类型,如year_month、month_day等可以按上述3种顺序,不使用第二个operator/

时区[编辑]

  • tzdb: 时区库
  • tzdb_list:tzdb的链表
  • get_tzdb
  • get_tzdb_list
  • reload_tzdb
  • remote_version
  • locate_zone函数:按照名字返回一个time_zone的地址。
  • current_zone 函数,返回当前的time_zone对象。其name如为“Asia/Shanghai”
  • time_zone类:表示一个时区。const访问。有成员函数name、get_info等。
  • sys_info 底层类,一般不用于编程。
  • local_info 类:represents information about a local time to UNIX time conversion
  • choose 枚举类,成员值为earliest,latest
  • zoned_traits
  • zoned_time 表示一个时区及时间点。也可做同一时间点不同时区的换算。例如:std::chrono::zoned_time zt{ "Asia/Shanghai",std::chrono::local_days{18d / std::chrono::January / 2022} + 15h + 15min }; std::chrono::zoned_time zt2{ "America/New_York", zt };
  • leap_second
  • leap_second_info 类:给出一个UTC时间点的闰秒总数。
  • get_leap_second_info 函数模板:给出一个UTC时间点返回leap_second_info实例。
  • time_zone_link 类:一个时区的可选名字。
  • nonexistent_local_time 异常类
  • ambiguous_local_time 异常类

chrono I/O[编辑]

std::chrono::parse函数模板:从输入流中分析一个chrono对象。

C++20的一个例子[编辑]

// C++20
#include <chrono>
#include <format>
#include <iostream>
 
int main()
{ 
  using TwentyMins =  std::chrono::duration<int, std::ratio<20*60>>;
 
   std::chrono::time_zone const* myTimeZone =  std::chrono::current_zone(); 
 
   std::chrono::time_point p1 =  std::chrono::system_clock::now();
   std::chrono::time_point p2 = myTimeZone->to_local(p1);
   std::chrono::time_point p3 =  std::chrono::floor<TwentyMins>(p2);
 
  std::cout << std::format("{:%Y-%m-%d %H:%M}\n", p3);
}

表示为字符串的示例[编辑]

#include <iostream>
#include <chrono> 
#include <ratio>
#include <time.h>
#include <iomanip>
#include <sstream>
#include <string>
int main()
{ 

	using namespace std::chrono; 
	auto now = system_clock::now(); 
	time_t t = system_clock::to_time_t(now); 
	std::tm tm;
	localtime_s(&tm, &t);
	auto time = std::put_time(&tm, "%b %d %Y %H:%M:%S");
	std::cout << time << std::endl;
	std::stringstream ss;
	ss << time;
	std::string a{ ss.str() }; 
}

例子[编辑]

时间点的示例[编辑]

#include <iostream>
#include <iomanip>
#include <ctime>
#include <chrono>
 
int main()
{
    std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
    std::time_t now_c = std::chrono::system_clock::to_time_t(now - std::chrono::hours(24));
    std::cout << "24 hours ago, the time was "
              << std::put_time(std::localtime(&now_c), "%F %T") << '\n';
 
    std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
    std::cout << "Hello World\n";
    std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
    std::cout << "Printing took "
              << std::chrono::duration_cast<std::chrono::microseconds>(end - start).count()
              << "us.\n";
}

时间长度的示例[编辑]

#include <iostream>
#include <chrono>
 
int main()
{
    using shakes = std::chrono::duration<int, std::ratio<1, 100000000>>;
    using jiffies = std::chrono::duration<int, std::centi>;
    using microfortnights = std::chrono::duration<float, std::ratio<12096,10000>>;
    using nanocenturies = std::chrono::duration<float, std::ratio<3155,1000>>;
 
    std::chrono::seconds sec(1);
 
    std::cout << "1 second is:\n";
 
    std::cout << std::chrono::duration_cast<shakes>(sec).count()
              << " shakes\n";
    std::cout << std::chrono::duration_cast<jiffies>(sec).count()
              << " jiffies\n";
    std::cout << microfortnights(sec).count() << " microfortnights\n";
    std::cout << nanocenturies(sec).count() << " nanocenturies\n";
}

综合示例[编辑]

#include <iostream>
#include <chrono>
#include <ctime>
 
long fibonacci(unsigned n)
{
    if (n < 2) return n;
    return fibonacci(n-1) + fibonacci(n-2);
}
 
int main()
{
    std::chrono::time_point<std::chrono::system_clock> start, end;
    start = std::chrono::system_clock::now();
    std::cout << "f(42) = " << fibonacci(42) << '\n';
    end = std::chrono::system_clock::now();
 
    std::chrono::duration<double> elapsed_seconds = end-start;
    std::time_t end_time = std::chrono::system_clock::to_time_t(end);
 
    std::cout << "finished computation at " << std::ctime(&end_time)
              << "elapsed time: " << elapsed_seconds.count() << "s\n";
}

参考文献[编辑]