date_time 库在格里高利历的基础上提供徼秒级别的时间系统,但如果需要,它最高可以达到纳秒级别的精确度。
date_time 库的时间功能位于名字空间boost::posix_time,为了使用时间组件,需要包含头文件
#include
using namespace boost::posix_time;
从概念上来说,(广义的)时间是日期的进一步细化,相当于在日期“天”的量级下增加了时分秒的分辨率,因此,我们首先介绍时间长度time_duration类,它表述了时分秒的度量,然后再介绍时间点ptime 类。
与日期长度date_duration类似,date_time库使用time_duration度量时间长度。
time_duration很像C中 tm 结构的时分秒部分,可以度量基本的小时、分钟和秒钟,在秒以下精确到微秒。如果在头文件
time_duration的类摘要如下:
class time_duration
{
public:
time_duration(); //构造函数
time_duration(hour_type, min_type, sec_type = 0, fractional_seconds_type = o);
time_duration(special_values);
hour_type hours() const; //获取时分秒
min_type minutes() const;
sec_type seconds() const;
sec_type total_seconds() const;
tick_type total_milliseconds() const;
tick_type total_microseconds() const;
tick_type total_nanoseconds() const;
fractional_seconds_type fractional_seconds() const;
duration_typeinvert_sign() const;
//正负号操作
bool is_negative() const;
bool operator<(const time_duration &) const;
//比较操作
bool operator==(const time_duration &) const;
bool is_special() const;
//有效性验证
bool is_pos_infinity() const;
bool is_neg_infinity() const;
bool is_not_a_date_time() const;
impl_typeget_rep() const;
tick_type ticks() const;
//时间分辨率
static duration_type unit();
static tick_type ticks_per_second();
static time_resolutions resolution();
static unsigned short num_fractional_digits();
};
为了方便使用,time_duration也有几个子类,可以度量不同的时间分辨率,它们的名字很容易记,分别是:hours.minutes.seconds.millisec/ milliseconds.microseclmicroseconds和 nanosec/nanoseconds。
time_duration支持全序比较操作和输入输出,而且比 date_duration要支持更多的算术运算,可以进行加减乘除全四则运算。
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的子类可以更直观地创建时间长度:
hours h(1); //1小时
minutes m(10); //10分钟
seconds s(30); //30秒钟
millisec ms(1); //1毫秒
time_duration td = h + m + s + ms; //可以赋值给time_duration
time_duration td2 = hours(2) + seconds(10); //可以直接赋值
使用工厂函数 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 td(1, 10, 30, 1000);
assert(td.hours() == 1 && td.minutes() == 10 && td.seconds() == 30);
assert(td.total_seconds() == 1 * 3600 + 10 * 60 + 30);
assert(td.total_milliseconds() == td.total_seconds() * 1000 + 1);
assert(td.fractional_seconds() == 1000);
time_duration可以取负值,专门有一个成员函数 is_negative()来判断它的正负号。成员函数invert_sign()可以将时间长度改变符号后生成一个新的时间长度:
hours h(-10);
assert(h.is_negative());
time_duration h2 = h.invert_sign();
assert(!h2.is_negative() && h2.hours() == 10);
time_duration也可以赋值为特殊时间值,包括not_a_date_time、pos_infin 等等,同样也有类似的 is_xxx()函数用于检测它是否为特殊时间,用法与date、date_duration类似,例如:
time_duration td1(not_a_date_time);
assert(td1.is_special() && td1.is_not_a_date_time());
time_duration td2(neg_infin);
assert(td2.is_negative() && td2.is_neg_infinity());
time_duration支持完整的比较操作和四则运算,因此它处理起来比date对象更加容易,例如:
time_duration td1 = hours(1);
time_duration td2 = hours(2) + minutes(30);
assert(td1 < td2);
assert((td1 + td2).hours() == 3);
assert((td1 - td2).is_negative());
assert(td1 * 5 == td2 * 2);
assert((td1 / 2).minutes() == td2.minutes());
如果想要得到 time_duration对象的字符串表示,可以使用自由函数to_simple.string(time_duration)和 to_iso_string(time_duration),它们分别返回HH : MM : SS.fffffffff和HHMMSS,fffffffff格式的字符串。例如:
time_duration td(1, 10, 30, 1000);
cout << to_simple_string(td) << endl;
cout << to_iso_string(td) << endl;
将输出:
01:10:30.001000
011030.001000
time_duration也可以转换到tm结构,同样使用to_tm()函数,但不能进行反向转换。
date_time 库默认时间的精确度是微秒,纳秒相关的类和函数如 nanosec 和成员函数nanoseconds ()、total_nanoseconds()都不可用,秒以下的时间度量都使用微秒。
当定义了宏BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG时,time_duration的一些行为将发生变化,它的时间分辨率将精确到纳秒,构造函数中秒以下的时间度量单位也会变成纳秒。
#ifdef BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG //定义纳秒精度宏
time_duration td(1, 10, 30, 1000); //1000纳秒,即1微妙
cout << td;
assert(td.total_milliseconds() == td.total_seconds() * 1000); //计算总毫秒数时微妙将被忽略
成员函数fractional_seconds()仍然返回秒的小数部分,但单位是纳秒,这也是它的名称不叫milli_seconds()或者nano_seconds()的原因:
assert(td.fractional_seconds() == 1000);
静态成员函数 unit()返回一个time_duration对象,它是time_duration计量的最小单位,相当于time_duration(0,0,0,1),默认情况下是微秒,如果定义了BOOST_DATE_TTME_POSTXTIMESTD_CONFIG则是纳秒:
assert(time_duration::unit() * 1000 * 1000 * 1000 == seconds(1)); //1秒等于10^9纳秒
time_duration提供静态成员函数resolution() 和 num_fractional_digits()来检测当前的精度:
1)resolution()可以返回一个枚举值 time_resolutions,表示时间长度的分辨率;
2)静态成员函数 num_fractional_digits()返回秒的小数部分的位数(微秒6位,纳秒9位):
assert(td.resolution() == boost::date_time::nano); //如果是纳秒分辨率则断言成立
assert(td.num_fractional_digits() == 9); //如果是纳秒分辨率则断言成立
BOOST_DATE_TINE_POSIX_TIME_STD_CONFIG宏主要影响time_duration构造函数中小数秒的解释,默认单位是微秒,定义了纳秒精度则单位是纳秒。如果要编写分辨率无关的代码,应尽量避免使用这种创建时间长度的方式,可以使用millisec/milliseconds、microsec/microseconds这些预先定义好的时间单位来创建时间长度。
成员函数ticks_per_second()是c中的宏 CLOCKS_PER_SEC的强化,它返回每秒钟内的tick数,也可以使用它来编写与时间精度无关的代码:
time_duration::tick_type my_millisec = time_duration::ticks_per_second()/1000; //自定义毫秒单位
time_duration td(1,10,30,10*my_millisec); //10毫秒,即0.01秒
因为在大多数情况下程序都不会用到纳秒级别的计时精度,微秒已经非常足够了,故以下将不再对纳秒进行讨论。但除了时间分辨率的粒度,date_time库的其他行为都是一致的。
在熟悉了时间长度类time_duration 后,理解时间点概念就容易多了,它相当于一个日期再加上一个小于一天的时间长度。如果时间轴的基本单位是天,那么日期就相当于整数,时间点则是实数,定义了天之间的小数部分。
ptime是date_time库处理时间的核心类,它使用64位(微秒级别)或者96位(纳秒级别)的整数在内部存储时间数据,依赖于date和 time_duration,因此接口很小。ptime的类摘要如下:
class ptime
{
public:
ptime(const date_type &, const time_duration_type &, dst_flags = not_dst); //构造函数
ptime (special_values);
date_type date() const; //获取日期和时间
time_duration_type time_of_day() const;
bool is_not_a_date_time() const; //有效性验证
bool is_infinity() const;
bool is_pos_infinity() const;
bool is_neg_infinity() const;
bool is_special() const;
bool operator==(const time_type &) const; //比较操作
bool operator<(const time_type &) const;
};
ptime同 date 一样,也是一个轻量级的对象,可以被高效地任意拷贝赋值,也支持全序比较和加减运算。
最基本的创建ptime的方式是在构造函数中同时指定date和 time_duration对象,令ptime等于一个日期加当天的时间偏移量。如果不指定time_duration,则默认为当天的零点。例如:
// posix_time 名字空间不包含gregorian名字空间,因此需要加上对它的引用
using namespace boost::gregorian ;
ptime p(date (2014,6,8), hours(1)); //2014年6月8日凌晨1时
ptime也可以从字符串构造,使用工厂函数time_from_string()和from_iso_string()。前者使用分隔符分隔日期时间成分,后者则是连续的数字,日期与时间用字母T隔开:
ptime p1 = time_from_string("2017-7-7 01:00:00");
ptime p2 = from_iso_string("20170707T010000");
date_time 库为ptime 也提供了时钟类,可以从时钟产生当前时间。因为时间具有不同的分辨率,所以有两个类second_clock 和 microsec_clock 分别提供秒级和微秒级的分辨率,它们的接口是相同的,local_time()获得本地当前时间,universal_time()获得UTC当前时间。例如:
ptime p1 = second_clock::local_time(); //秒精度
ptime p2 = microsec_clock::universal_time(); //微妙精度
cout << p1 << endl << p2;
ptime 可以构造为特殊时间值,如无效时间、无限时间,也同样有is_xxx()来检验特殊值:
ptime p1(not_a_date_time); //无效时间
assert(p1.is_not_a_date_time());
ptime p2(pos_infin); //正无限时间
assert(p2.is_special() && p2.is_pos_infinity());
由于 ptime相当于date+time_duration,因此对它的操作可以分解为对这两个组成部分的操作。
ptime使用date()和time_of_day()两个成员函数获得时间点中的日期和时间长度,然后就可以分别处理。例如:
ptime p(date(2010, 3, 20), hours(12) + minutes(30));
date d = p.date();
time_duration td = p.time_of_day();
assert(d.month() == 3 && d.day() == 20);
assert(td.total_seconds() == 12 * 3600 + 30 * 60);
ptime支持比较操作与加减运算,运算规则与日期类似:
ptime p1(date(2010, 3, 20), hours(12) + minutes(30));
ptime p2 = p1 + hours(3);
assert(p1 < p2);
assert(p2 - p1 == hours(3));
p2 += months(1);
assert(p2.date().month() == 4);
ptime提供了三个自由函数转换为字符串,分别是:
to_simple_string() 转换为YYYY-mmm-DD HH:MM:SS.ffffff格式
to_iso_string() 转换为YYYYMMDDTHHMIMSS,ffffff格式
to_iso_extended_string() 转换为YYYY-MM-DDTHH:MM:SS,ffffff格式
其中的ffffff是秒的小数部分,如果为0则不显示,T是日期与时间的分隔符。
示范这三个函数用法的代码如下:
ptime p(date(2017, 2, 14), hours(20));
cout << to_simple_string(p) << endl;
cout << to_iso_string(p) << endl;
cout << to_iso_extended_string(p) << endl;
程序运行结果如下:
2014-Feb-14 20:00:00
20140214T200000
2014-02-14T20:00:00
使用自由函数to_tm(),ptime可以单向转换到tm结构,转换规则是date和 time_duration的组合。例如:
ptime p(date(2017, 5, 20), hours(14));
tm t = to_tm(p);
assert(t.tm_year == 117 && t.tm_hour == 14);
没有一个叫做time_from_tm()的函数可以把tm结构转换成ptime,这与date对象的date_from_tm()是不同的。
如果想要把tm转换成ptime,可以使用date_from_tm()得到date对象,然后手工操作 tm 结构得到time_duration对象,最后创建出ptime。
另有一个函数from_time_t(time_t),它们可以从time_t结构创建出 ptime对象。这种转换也是单向的,不存在逆向的转换。例如:
ptime p2 = from_time_t(std::time(0));
assert(p2.date() == day_clock::local_day());
与日期区间date_period对应,date_time库也有时间区间的概念,类time_period使用ptime 作为区间的两个端点,同样是左闭右开区间。
time_period 的用法与date_period基本相同,可以用begin()和 last()返回区间的两个端点,length()返回区间的长度,shift()和expand()变动区间,也能计算时间区间的交集和并集——就像是 date_period 的一个时间分辨率的增强版。
示范time_period一些用法的代码如下:
ptime p(date(2017, 1, 1), hours(12)); //2017年元旦中午
time_period tp1(p, hours(8)); //一个8小时区间
time_period tp2(p + hours(8), hours(1)); //1小时的区间
assert(tp1.end() == tp2.begin() && tp1.is_adjacent(tp2));
assert(!tp1.intersects(tp2)); //两个区间相邻但不相交
tp1.shift(hours(1)); //tp1平移1小时
assert(tp1.is_after(p)); //tp1在中午之后
assert(tp1.intersects(tp2)); //两个区间现在相交
tp2.expand(hours(10)); //tp2向两端扩展10个小时
assert(tp2.contains(p) && tp2.contains(tp1));
不同于日期迭代器,时间迭代器只有一个time_iterator。它在构造时传入一个起始时间点 ptime对象和一个步长 time_duration对象,然后就同日期迭代器一样使用前置式operator++、operator--
来递增或递减时间,解引用操作符返回一个ptime对象。
time_iterator也可以直接与ptime 比较,无须再使用解引用操作符。
ptime p(date(2017, 5, 31), hours(10));
for (time_iterator t_iter(p, minutes(10)); t_iter < p + hours(1); ++t_iter)
{
cout << *t_iter << endl;
}
高精度计时器
我们首先利用ptime来实现一个高精度的计时器ptimer,用来取代之前介绍的计时器timer和 progress_timer。
ptimer的原理与timer一样,在对象创建时就启动计时,然后可以用elapsed()函数返回流逝的时间,析构时也会自动输出时间。与timer不同的是,ptimer利用了date_time库的高精度时间度量功能,总可以提供微秒级别的计时。如果需要,它还可以精确到纳秒级别。
ptimer的缺陷是它依赖于date_time 库,需要编译date_time 才能使用。
为了支持date_time库的两个时钟类:second_clock和 microsec_clock,我们决定把ptimer实现为一个模板类basic_ptimer,然后用定制时钟类的方式就可以实现两个精度分别为秒级别和微秒级别的计时器。
由于date_time库提供了大量的便利操作,因此basic_ptimer的实现代码相当简单:
template<typename Clock = microsec_clock> //缺省使用microsec_clock
class basic _ptimer
{
public:
basic_ptimer() //构造函数,初始化起始时间
{ restart(); }
void restart() //重新开始计时
{ _start_time = Clock::local_time(); }
void elapsed() const //度量流逝的时间
{ cout << clock::local_time() - _start_time; }
~basic_ptimer() //析构函数自动输出时间
{ elapsed(); }
private:
ptime _start_time; //私有变量,,保存计时开始时间
};
typedef basic_ptimer<microsec_clock> ptimer; //typedef具体类
typedef basic_ptimer<second_clock> sptimer;
ptimer可以如timer一样使用,例如:
int main()
{
ptimer t; //创建ptimer计时器对象
... //花费时间的任意操作
//退出作用域自动输出时间
}
计算工作时间
接下来我们使用time_period类来表示工作日的工作时间,并根据当前时间给用户一个友好的提示信息。
先不考虑节假日等特殊因素,假设工作制为每天8小时,早上 9:00上班,中午 12:30到13:30为午餐时间,下午 6:00 下班。为了便于处理,我们把一天分成五个时间段,分别表示上班前、上午、中午、下午和下班后。由于 time_period支持比较操作,故可以使用std::map来保存时间段与问候语的对应关系。
我们定义一个work_time类,它封装了这些逻辑:
class work__time
{
//接下来是关联容器map的类型定义,用于简化代码:
public:
typedef map<time_period, string> map_t; // time_period映射到string
//成员变量map_ts保存了时间段与问候语的对应关系:
private:
map_t map_ts;
//成员函数init()是work_time类的核心,初始化时间段与问候语,向map_ts插入数据:
void init()
{
ptime p(day_clock::local_day()); //获得当天的日期
map_ts[time_period(p, hours(9))] = "It's too early, just relax.\n";
p += hours(9); //到早上9点
map_ts[time_period(p, hours(3) + minutes(30))] = "It's AM,please work hard.\n";
p += hours(3) + minutes(30); //到12:30
map_ts[time_period(p, hours(1))] = "It's lunch time,are you hungry?\n" ;
p += hours(1); //到13:30
map_ts [time_period(p, hours(4) + minutes(30))] = "It's PM, ready to go home.\n";
p += hours(4) + minutes(30); //到下午6点
map_ts[time_period(p, hours(6))] = "Are you still working? you do need a rest.\n";
}
//work_time的构造函数调用init()初始化map_ts:
public:
work_time()
{ init(); }
//最后是给出提示信息的greeting()函数,它使用for遍历map容器
//判断时间点是否在某个时间区间内,如果是则输出对应的问候语:
void greeting(const ptime& t)
{
for(auto&x : map_ts) //使用C++11的for+auto
{
if(x.first.contains(t)) //pair的first成员是时间段
{
cout << x.second << endl;
break;
} //end if
} //end for
}
}; //work_time类结束
//work_time类的使用如下:
int main()
{
work_time wt; //创建一个work_time对象
wt.greeting(second_clock::local_time()); //用当前时间致问候语
}
#include
using namespace std;
//#define BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG
#include
using namespace boost::gregorian;
#include
using namespace boost::posix_time;
//
void case1()
{
{
time_duration td = duration_from_string("1:10:30:001");
cout << td << endl;
time_duration td1(1, 10, 30, 1000);
time_duration td2(1, 60, 60, 1000 * 1000 * 6 + 1000);
}
hours h(1);
minutes m(10);
seconds s(30);
millisec ms(1);
time_duration td = h + m + s + ms;
time_duration td2 = hours(2) + seconds(10);
cout << td << td2 << endl;
}
//
void case2()
{
time_duration td(1, 10, 30, 1000);
assert(td.hours() == 1 && td.minutes() == 10 && td.seconds() == 30);
assert(td.total_seconds() == 1 * 3600 + 10 * 60 + 30);
#ifndef BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG
assert(td.total_milliseconds() == td.total_seconds() * 1000 + 1);
assert(td.fractional_seconds() == 1000);
#endif
hours h(-10);
assert(h.is_negative());
time_duration h2 = h.invert_sign();
assert(!h2.is_negative() && h2.hours() == 10);
time_duration td1(not_a_date_time);
assert(td1.is_special() && td1.is_not_a_date_time());
time_duration td2(neg_infin);
assert(td2.is_negative() && td2.is_neg_infinity());
}
//
void case3()
{
time_duration td1 = hours(1);
time_duration td2 = hours(2) + minutes(30);
assert(td1 < td2);
assert((td1 + td2).hours() == 3);
assert((td1 - td2).is_negative());
assert(td1 * 5 == td2 * 2);
assert((td1 / 2).minutes() == td2.minutes());
time_duration td(1, 10, 30, 1000);
cout << to_simple_string(td) << endl;
cout << to_iso_string(td) << endl;
}
//
void case4()
{
#ifdef BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG
time_duration td(1, 10, 30, 1000);
cout << td;
assert(td.total_milliseconds() ==
td.total_seconds() * 1000);
assert(td.fractional_seconds() == 1000);
assert(time_duration::unit() * 1000 * 1000 * 1000 == seconds(1));
assert(td.resolution() == boost::date_time::nano);
assert(td.num_fractional_digits() == 9);
#endif
}
//
void case5()
{
ptime p(date(2017, 7, 7), hours(1));
ptime p1 = time_from_string("2017-7-7 01:00:00");
ptime p2 = from_iso_string("20170707T010000");
cout << p1 << endl << p2;
{
ptime p1 = second_clock::local_time();
ptime p2 = microsec_clock::universal_time();
cout << p1 << endl << p2;
}
}
//
void case6()
{
ptime p(date(2010, 3, 20), hours(12) + minutes(30));
date d = p.date();
time_duration td = p.time_of_day();
assert(d.month() == 3 && d.day() == 20);
assert(td.total_seconds() == 12 * 3600 + 30 * 60);
ptime p1(date(2010, 3, 20), hours(12) + minutes(30));
ptime p2 = p1 + hours(3);
assert(p1 < p2);
assert(p2 - p1 == hours(3));
p2 += months(1);
assert(p2.date().month() == 4);
cout << endl;
{
ptime p(date(2017, 2, 14), hours(20));
cout << to_simple_string(p) << endl;
cout << to_iso_string(p) << endl;
cout << to_iso_extended_string(p) << endl;
}
}
//
void case7()
{
ptime p(date(2017, 5, 20), hours(14));
tm t = to_tm(p);
assert(t.tm_year == 117 && t.tm_hour == 14);
assert(ptime_from_tm(t) == p);
ptime p2 = from_time_t(std::time(0));
assert(p2.date() == day_clock::local_day());
cout << to_time_t(p2) << endl;
}
//
void case8()
{
ptime p(date(2017, 1, 1), hours(12));
time_period tp1(p, hours(8));
time_period tp2(p + hours(8), hours(1));
assert(tp1.end() == tp2.begin() && tp1.is_adjacent(tp2));
assert(!tp1.intersects(tp2));
tp1.shift(hours(1));
assert(tp1.is_after(p));
assert(tp1.intersects(tp2));
tp2.expand(hours(10));
assert(tp2.contains(p) && tp2.contains(tp1));
}
//
void case9()
{
ptime p(date(2017, 5, 31), hours(10));
for (time_iterator t_iter(p, minutes(10));
t_iter < p + hours(1); ++t_iter)
{
cout << *t_iter << endl;
}
}
//
template<typename Clock = microsec_clock>
class basic_ptimer
{
public:
basic_ptimer()
{
restart();
}
void restart()
{
_start_time = Clock::local_time();
}
void elapsed() const
{
cout << Clock::local_time() - _start_time;
}
~basic_ptimer()
{
elapsed();
}
private:
ptime _start_time;
};
typedef basic_ptimer<microsec_clock> ptimer;
typedef basic_ptimer<second_clock> sptimer;
class work_time
{
public:
typedef map<time_period, string> map_t;
private:
map_t map_ts;
void init()
{
ptime p(day_clock::local_day());
map_ts[time_period(p, hours(9))] = "It's too early, just relax.\n";
p += hours(9);
map_ts[time_period(p, hours(3) + minutes(30))] = "It's AM, please work hard.\n";
p += hours(3) + minutes(30);
map_ts[time_period(p, hours(1))] = "It's lunch time, are you hungry?\n";
p += hours(1);
map_ts[time_period(p, hours(4) + minutes(30))] = "It's PM, ready to go home.\n";
p += hours(4) + minutes(30);
map_ts[time_period(p, hours(6))] = "Are you still working? you do need a rest.\n";
}
public:
work_time()
{
init();
}
void greeting(const ptime& t)
{
for (auto& x : map_ts)
{
if (x.first.contains(t))
{
cout << x.second << endl;
break;
}
}
}
};
void case10()
{
ptimer t;
work_time wt;
wt.greeting(second_clock::local_time());
}
//
int main()
{
case1();
case2();
case3();
case4();
case5();
case6();
case7();
case8();
case9();
case10();
}