2.6 处理时间
date_time库在格里高利历的基础上提供了微秒级别的时间系统,如果需要,它最高可以达到纳秒级别的精确度。
date_time库的时间功能位于名字空间boost::posix_time,需要包含的头文件如下:
从概念上来说,时间(广义的)是日期的进一步细化,相当于在日期“天”的量级下增加了“时、分、秒”,所以,我们首先介绍时间长度time_duration,它表述了时、分、秒的度量,然后介绍时间点ptime。
2.6.1 时间长度
与日期长度date_duration类似,date_time库使用time_duration度量时间长度。
time_duration很像C语言中tm结构中的时、分、秒部分,可以度量基本的小时、分钟和秒钟,还可以精确到微秒。如果在头文件之前定义了宏BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG,那么它还可以精确到纳秒。以下我们主要叙述微秒精度的time_duration。
time_duration的类摘要如下:
为了方便使用,time_duration也有几个子类,可以度量不同的时间分辨率,它们的名字很容易记,分别是:hours、minutes、seconds、millisec、microsec和nanosec。
time_duration支持全序比较操作和输入输出,而且比date_duration支持的算术运算要多,可以用它进行加减乘除四则运算。
2.6.2 操作时间长度
time_duration可以在构造函数时指定时、分、秒、微秒,如创建一个1小时10分钟30秒1毫秒(1000微秒)的时间长度:
time_duration td(1,10,30,1000);
时、分、秒等值可以是任意数值,不一定必须在它们的限度里,超出的时间会自动进位或借位。例如,下面的代码表示2小时01分06.001秒:
time_duration td(1,60,60,1000*1000*6+1000);
使用time_duration的子类可以更直观地创建时间长度:
使用工厂函数duration_from_string(),time_duration也可以从一个字符串创建,字符串中的时、分、秒、微秒需要用冒号隔开:
time_duration td=duration_from_string("1:10:30:001");
time_duration里的时、分、秒可以用hours()、minutes()、seconds()成员函数访问。total_seconds()、total_milliseconds()、total_microseconds()分别返回时间长度的总秒数、总毫秒数和总微秒数。fractional_seconds()以long返回微秒数:
time_duration允许取负值,有一个成员函数is_negative()专门来判断它的正负号。成员函数invert_sign()可以改变时间长度符号,生成一个新的时间长度:
time_duration也可以赋值特殊时间值,包括not_a_date_time、pos_infin等,同样也有类似的is_xxx()函数用于检测它是否为特殊时间,其用法与date、date_duration类似。例如:
time_duration支持完整的比较操作和四则运算,因此它比date对象更容易处理。例如:
如果想要得到time_duration对象的字符串表示,可以使用自由函数to_simple_string()和to_iso_string(),它们分别返回HH:MM:SS.fffffffff和HHMMSS,fffffffff格式的字符串。例如:
输出结果如下:
time_duration也可以使用to_tm()函数转换到tm结构,但不能进行反向转换。
2.6.3 时间精确度
date_time库默认时间精确度是微秒,纳秒相关的类和函数(如nanosec和成员函数nanoseconds()、total_nanoseconds())都不可用,秒以下的时间度量都使用微秒。当定义了宏BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG时,time_duration的一些行为将发生变化,它的时间分辨率将精确到纳秒,构造函数中秒以下的时间度量单位也会变成纳秒。
请读者将以下代码与2.6.2节的time_duration构造进行比较:
成员函数fractional_seconds()仍然返回秒的小数部分,但单位是纳秒,这也是它的名称不是milli_seconds()或nano_seconds()的原因:
静态成员函数unit()返回一个time_duration对象,它是time_duration计量的最小单位,相当于time_duration(0,0,0,1),在默认情况下其单位是微秒,如果定义了宏BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG,则其单位是纳秒:
time_duration提供静态成员函数resolution()和num_fractional_digits()来检测当前的时间精度。
■ resolution()返回一个枚举值time_resolutions,表示时间长度的分辨率。
■ 静态成员函数num_fractional_digits()返回秒的小数部分的位数(微秒为6位,纳秒为9位)。
BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG宏主要影响time_duration构造函数中小数秒的解释,默认单位是微秒,如果定义了纳秒精度,则单位是纳秒。如果要编写与分辨率无关的代码,应尽量避免使用这种创建时间长度的方式,可以使用millisec/milliseconds、microsec/microseconds这些预先定义好的时间单位来创建时间长度。成员函数ticks_per_second()是C中的宏CLOCKS_PER_SEC的强化,它返回每秒钟内的tick数,可以使用它来编写与时间精度无关的代码:
在大多数情况下,程序都不会用到纳秒级别的计时精度,微秒级别的计时精度已经足够了,故不再对纳秒进行讨论。但除了时间分辨率的粒度,date_time库的其他行为都是一致的。
2.6.4 时间点
在熟悉了时间长度类time_duration后,理解时间点的概念就容易多了,它相当于一个日期再加上一个小于24小时的时间长度。如果时间轴的基本单位是天,那么日期就相当于整数,则时间点是实数,定义了天之间的小数部分。
ptime是date_time库处理时间的核心类,它使用64位(微秒级别)或96位(纳秒级别)的整数在内部存储时间数据,依赖于date和time_duration,因此其接口很小。ptime的类摘要如下:
ptime也是个轻量级的对象,可以被高效地任意拷贝、赋值,也支持全序比较运算和加减运算。
2.6.5 创建时间点对象
最基本的创建ptime的方式是在构造函数中同时指定date和time_duration对象,令ptime等于一个日期加当天的时间偏移量。如果不指定time_duration,则默认为当天的零点。例如:
ptime也可以从字符串构造,使用工厂函数time_from_string()和from_iso_string()即可。前者使用分隔符分隔日期时间,后者则是连续的数字,日期与时间用字母T隔开:
date_time库也为ptime提供了时钟类,可以从时钟产生当前时间。因为时间具有不同的分辨率,所以有second_clock和microsec_clock两个类分别提供秒级和微秒级的分辨率,它们的接口是相同的,local_time()获得本地的当前时间,universal_time()获得UTC的当前时间:
ptime可以构造为特殊时间值,如无效时间、无限时间,同样有is_xxx()来检验特殊值:
2.6.6 操作时间点对象
由于ptime相当于date+time_duration,所以对它的操作可以分解为对这两个组成部分的操作。
ptime使用date()和time_of_day()两个成员函数获得时间点中的日期长度和时间长度,然后就可以分别对其进行处理。例如:
ptime支持比较操作与加减运算,其运算规则与日期类似:
ptime提供了3个自由函数转换为字符串,具体如下。
■ to_simple_string():转换为YYYY-mmm-DD HH:MM:SS.ffffff格式;
■ to_iso_string():转换为YYYYMMDDTHHMMSS,ffffff格式;
■ to_iso_extended_string():转换为YYYY-MM-DDTHH:MM:SS,ffffff格式。
其中的ffffff是秒的小数部分,如果为0则不显示,T是日期与时间的分隔符。
示范上述3个函数用法的代码如下:
程序运行结果如下:
2.6.7 转换C结构
使用函数to_tm(),ptime可以转换到tm结构,转换规则是将date和time_duration组合,而使用ptime_from_tm()函数可以把tm结构转换成ptime。例如:
函数from_time_t()和to_time_t()可以在time_t和ptime之间互相转换:
2.6.8 时间区间
与日期区间date_period对应,date_time库也有时间区间的概念,time_period使用ptime作为时间区间的两个端点,时间区间同样是左闭右开区间。
time_period的用法与date_period基本相同,可以用begin()和last()返回时间区间的两个端点;用length()返回时间区间的长度;用shift()和expand()可以变动时间区间,也能计算时间区间的交集和并集——就像是date_period的一个时间分辨率的增强版。所以,time_period的详细操作函数可参考date_period(2.5.8节)。
示范time_period的一些用法的代码如下:
2.6.9 时间迭代器
不同于日期迭代器,时间迭代器只有一个time_iterator。它在构造时传入一个起始时间点ptime对象和一个步长time_duration对象,然后就同日期迭代器一样使用前置式operator++、operator--来递增或递减时间,使用解引用操作符返回一个ptime对象。
time_iterator也可以直接与ptime比较,无须再使用解引用操作符。
下面的代码使用时间迭代器以10分钟为步长打印时间:
2.6.10 综合运用
本节将综合运用与ptime相关的所有类并给出一些具体的使用例子。
1.高精度计时器
我们首先利用ptime来实现一个高精度计时器ptimer,用来取代2.2节介绍的计时器timer和2.3节介绍的progress_timer。
ptimer的原理与timer一样,在创建对象时就启动计时,然后可以用elapsed()函数返回流逝的时间,析构时也会自动输出时间。与timer不同的是,ptimer利用了date_time库的高精度时间度量功能,总可以提供微秒级别的计时。如果需要,ptimer还可以精确到纳秒级别。
为了支持date_time库的两个时钟类(second_clock和microsec_clock),我们决定把ptimer实现为一个模板类basic_ptimer,然后用定制时钟类的方式就可以实现两个精确度分别为秒级别和微秒级别的计时器。
由于date_time库提供了大量的便利操作,所以basic_ptimer的实现代码相当简单:
可以像使用timer一样使用ptimer。例如:
2.计算工作时间
接下来我们使用time_period来表示工作日的工作时间,并根据当前时间给用户一个友好的提示信息。
先不考虑节假日等特殊因素,假设工作制为每天8小时,早上9:00上班,中午12:30到13:30为午餐时间,下午6:00下班。为了便于处理,我们把一天分成五个时间段,分别表示上班前、上午、中午、下午和下班后。由于time_period支持比较操作,故可以使用std::map来保存时间段与问候语的对应关系。我们定义一个work_time,用来封装这些逻辑:
class work_time
{...};
接下来介绍关联容器map的类型定义,用于简化代码:
成员函数init()是work_time的核心,可以用它初始化时间段与问候语,向map_ts插入数据:
最后介绍给出提示信息的greeting()函数,它使用for遍历map容器,判断时间点是否在某个时间区间内,如果是则输出对应的问候语:
如果现在是上午,那么程序的运行结果如下:
It's AM,please work hard.