Boost开发指南-2.4date_time

date_time

date_time库勇敢地面对了这个挑战,并成功地解决了大部分问题。它是一个非常全面且灵活的日期时间库,基于我们日常使用的公历(即格里高利历),可以提供时间相关的各种所需功能,如精确定义的时间点、时间段和时间长度、加减若干天/月/年、日期迭代器等等。date_time库还支持无限时间和无效时间这种实际生活中有用的概念,而且可以与c的传统时间结构tm相互转换,提供向下支持。

编译与使用

date_time库需要编译才能使用,在jamfile里指定lib的语句是:

lib boost_data_time;

date_time 库包含两个部分,分别是处理日期的 gregorian和处理时间的posix_time,需要包含如下的头文件:

//处理日期的组件
#inlcude <boost/date_time/gregorian/gregorian.hpp>
using namespace boost::gregorian;

//处理时间的组件
#include <boost/date_time/posix_time/posix_time.hpp>
using namespace boost::posix_time;

基本概念

时间的处理很复杂,因此在使用date_time库之前,我们需要明确一些基本概念。

如果把时间想象成一个向前和向后都无限延伸的实数轴,那么时间点就是数轴上的一个点,时间段就是两个时间点之间确定的一个区间,时长(时间长度)则是一个有正负号的标量,它是两个时间点之差,不属于数轴。

时间点、时间段和时长三者之间可以进行运算,例如时间点+时长=时间点,时长+时长=时长,时间段∩时间段=时间段、时间点∈时间段等等,但有的运算也是无意义的,如时间点+时间点、时长+时间段等等。这些运算都基于生活常识,很容易理解,但在编写时间处理程序时必须注意。

date_time库支持无限时间和无效时间(NADT,Not Available Date Time)这样特殊的时间概念,类似于数学中极限的含义。时间点和时长都有无限的值,它们的运算规则比较特别,例如+ oo时间点+时长=+∞时间点,时间点+∞时长=+∞时间点。如果正无限值与负无限值一起运算将有可能是无效时间,如+∞时长-时长=NADT。

date_time库中用枚举special_values定义了这些特殊的时间概念,它位于名字空间boost::date_time,并被using语句引入其他子名字空间。

pos_infin                   表示正无限
neg_infin                   表示负无限
not_a_date_time             无效时间
min_date_time               可表示的最小日期或时间
max_date_time               可表示的最大日期或时间

处理日期

我们首先研究date_time库的日期处理部分,因为日期只涉及年月日,较时间少处理时分秒三个量,相当于数轴上的整数,要容易学习一些。

date_time库的日期基于格里高利历,支持从1400-01-01到9999-12-31之间的日期计算(很遗憾,它不能处理公元前的日期,不能用它来研究古老的历史)。它位于名字空间boost:: gregorian,为了使用date_time库的日期功能,需要包含头文件<boost/date_time/gregorian/gregorian.hpp>,即:

#include <boost/date_time/gregorian/gregorian.hpp>
using namespace boost::gregorian;

日期

date是 date_time 库处理日期的核心类,使用一个32位的整数作为内部存储,以天为单位表示时间点概念。它的类摘要如下:

template<typename T, typename calendar, typename duration_type_>
class date {
public:
   date(year_type, month_type, day_type); //构造函数
   date(const ymd_type &);
   
   year_type          year() const; //基本操作函数
   month_type         month() const;
   day_type           day() const;
   day_of_week_type   day_of_week() const;
   ymd_type           year_month_day() const;
   
   bool operator<(const date_type &) const; //比较操作函数
   bool operator==(const date_type &) const;
   
   bool is_special() const; //有效性验证
   bool is_not_a_date() const;
   bool is_infinity() const;
   bool is_pos_infinity() const;
   bool is_neg_infinity() const;
   special_values as_special() const;
   duration_type operator-(const date_type &) const;
   ...   //其他日期运算
};

date是一个轻量级的对象,很小,处理效率很高,可以被拷贝传值。date也全面支持比较操作和流输入输出,因此我们完全可以把它当成一个像int、string那样的基本类型来使用。

创建日期对象

有很多种方式可以创建一个日期对象。

空的构造函数会创建一个值为not_a_date_time的无效日期;顺序传入年月日值则创建一个对应日期的date对象,例如:

date d1; //一个无效的日期
date d2(2010, 1, 1); //使用数字构造日期
date d3(2000, Jan, 1); //也可以使用英文指定月份
date d4(d2); //date支持拷贝构造

assert(d1 == date(not_a_date_time)); //比较一个临时对象
assert(d2 == d4); //date支持比较操作
assert(d3 < d4);

date 也可以从一个字符串产生,这需要使用工厂函数from_string()或fromundelimited_string()。前者使用分隔符(斜杠或者连字符)分隔年月日格式的字符串,后者则是无分隔符的纯字符串格式。例如:

date d1 = from_string("1999-12-31");
date d2 (from_string("2015/1/1"));
date d3 = from_undelimited_string("20011118");

day_clock 是一个天级别的时钟,它也是一个工厂类,调用它的静态成员函数local_day()universal_day()会返回一个当天的日期对象,分别是本地日期和UTC日期。day_clock 内部使用了c标准库的 localtime()gmtime()函数,因此local_day()的行为依赖于操作系统的时区设置。例如:

cout << day_clock::local_day() << endl;
cout << day_clock::universal_day() << endl;

我们也可以使用特殊时间概念枚举 special_values 创建一些特殊的日期,在处理如无限期的某些情形下会很有用:

date d1(neg_infin); //负无限日期
date d2(pos_infin); //正无限日期
date d3(not_a_date_time): //无效日期
date d4(max_date_time); //最大可能日期9999-12-31
date d5(min_date_time);  //最小可能日期1400-01-01

使用cout将它们输出,显示将会是:

-infinity
+infinity
not-a-date-time
9999-Dec-31
1400-Jan-01

如果创建日期对象时使用了非法的值,例如日期超过了1400-01-01到9999-12-31的范围,或者使用不存在的月份或日期,那么date_time库会抛出异常(而不是转换为一个无效日期),可以使用what ()获得具体的错误信息。

下面的date对象构造均会抛出异常:

date d1(1399, 12, 1); //超过日期下限
date d2(10000, 1, 1); //超过日期上限
date d3(2010, 2, 29); //不存在的日期

访问日期

date类的对外接口很像c语言中的tm结构,也可以获取它保存的年、月、日、星期等成分,但 date提供了更多的操作。

成员函数year() 、 month()和 day()分别返回日期的年、月、日:

date d(2014, 4, 1);
assert(d.year() == 2014);
assert(d.month() == 4);
assert(d.day() == 1);

成员函数year_month_day()返回一个date::ymd_type结构,可以一次性地获取年月日数据:

date::ymd_type ymd = d.year_month_day();
assert(ymd.year == 2014);
assert(ymd.month == 4);
assert(ymd.day == 1);

成员函数 day_of_week ()返回date 的星期数,0表示星期天。day_of_year()返回date是当年的第几天(最多是366)。end_of_month()返回当月的最后一天的date对象:

cout << d.day_of_week() << endl; //输出Tue
cout << d.day_of_year() << endl; //输出91
assert(d.end_of_month() == date(2014, 4, 30));

成员函数week_number()返回date所在的周是当年的第几周,范围是0至53:

cout << date(2014, 1, 10).week_number() << endl; //输出2
cout << date(2014, 1, 1).week_number() << endl; //输出1
cout << date(2015, 1, 1).week_number() << endl; //输出1

date还有五个is_xxx()函数,用于检验日期是否是一个特殊日期,它们是:

is_infinity()       是否是一个无限日期
is_neg_infinity()   是否是一个负无限日期
is_pos_infinity()   是否是一个正无限日期
is_not_a_date()     是否是一个无效日期
is_special()        是否是任意一个特殊日期

他们用法如下:

assert(date(pos_infin).is_infinity());
assert(date(pos_infin).is_pos_infinity());
assert(date(neg_infin).is_neg_infinity());
assert(date(not_a_date_time).is_not_a_date());
assert(date(not_a_date_time).is_special());
assert(!date(2014,11,1).is_special());

日期的输出

date对象可以很方便地转换成字符串,它提供了三个自由函数。

to_simple_string(date d):           转换为YYYY-mmm-DD格式的字符串,其中的mmm为3字符的英文月份名
to_iso_string(date d):              转换为YYYYMMDD格式的数字字符串
to_iso_extended_string(date d):     转换为YYYY-MM-DD格式的数字字符串

date也支持流输入输出,默认使用YYYY-mmm-DD格式。例如:

date d(2008, 11, 20);
cout << to_simple_string(d) << endl;
cout << to_iso_string(d) << endl;
cout << to_iso_extended_string(d) << endl;
cout << d << endl;

cin >> d;
cout << d;

程序的运行结果如下:

2008-Nov-20
20081120
2008-11-20
2008-Nov-20
2010-Jan-02(用户的输入)
2010-Jan-02

转换tm结构

date支持与c标准库中的tm结构相互转换,转换的规则和函数如下:

to_tm(date)  date转换到tm。tm的时分秒成员(tm_hour, tm_min, tm_sec)均置为0,夏令时标志tm_isdst置为-1(表示未知)
date_from_tm(tm datetm) tm转换到date。只使用年、月、日三个成员(tm_year, tm_mon, tm_mday), 其他成员均被忽略。

下面的代码示范了date 与tm的相互转换:

date d(2014, 2, 1);
tm t = to_tm(d);
assert(t.tm_hour == 0 && t.tm_min == 0);
assert(t.tm_year == 114 && t.tm_mday == 1);

date d2 = date_from_tm(t);
assert(d == d2);

日期长度

日期长度是以天为单位的时长,是度量时间长度的一个标量。它与日期不同,值可以是任意的整数,可正可负。基本的日期长度类是date_duration,它的类摘要如下:

class date_duration
{
public:
   date_duration(long); //构造函数
   date_duration(special_values); 
   long days() const; //成员访问函数
   bool is_special() const;
   bool is_negative() const;
   bool operator==(const date_duration &) const;
   ... //其他操作符定义
   static date_duration unit(); //时长单位
};

date_duration可以使用构造函数创建一个日期长度,成员函数days()返回时长的天数,如果传入特殊时间枚举值则构造出一个特殊时长对象。is_special()is_negative()可以判断date_duration对象是否为特殊值、是否是负值。unit()返回时长的最小单位,即date_duration(1)

date_duration支持全序比较操作(==、!=、<、<=等),也支持完全的加减法和递增递减操作,用起来很像一个整数。此外 date_duration还支持除法运算,可以除以一个整数,但不能除以另一个date_duration,其他的数学运算如乘法、取模、取余则不支持。

date_time库为date_duration定义了一个常用的typedef: days,这个新名字更好地说明了date_duration的含义——它是一个天数的计量。

示范days (date_duration)用法的代码如下:

days dd1(10), dd2(-100), dd3(255);

assert(dd1 > dd2 && dd1 < dd3);
assert(dd1 + dd2 == days(-90));
assert((dd1 + dd3).days() == 265);
assert(dd3 / 5 == days(51));

为了方便计算时间长度,date_time库还提供了months、years、weeks等另外三个时长类,分别用来表示月、年和星期,它们的含义与days类似,但行为不太相同。

months和 years 全面支持加减乘除运算,使用成员函数number_of_months()number_of_years()可获得表示的月数和年数。weeks是date_duration的子类,除了构造函数以7为单位外其他的行为与days完全相同,可以说是一个days的近义词。

示范这三个时长类基本用法的代码如下:

weeks w(3); //3个星期
assert(w.days() == 21);
months m(5); //5个月
years y(2); //2年
months m2 = y + m;
assert(m2.number_of_months() == 29); //2年零5个月
assert((y * 2).number_of_years() == 4);

日期运算

date支持加减运算,两个date对象的加操作是无意义的(date_time库会以编译错误的方式通知我们),date主要是与时长概念配合运算。

例如,下面的代码计算了从2000年1月1日到2014年11月18日的天数,并执行其他的日期运算:

#include <boost/date_time/gregorian/gregorian.hpp>
using namespace boost::gregorian;

int main()
{
	date d1(2000, 1, 1), d2(2017, 11, 18);
	cout << d2 - d1 << endl; //5435天
	assert(d1 + (d2 - d1) == d2);

	d1 += days(10); //2000-1-11
	assert(d1.day() == 11);
	d1 += months(2); //2000-3-11
	assert(d1.month() == 3 && d1.day() == 11);
	d1 -= weeks(1); //2000-3-04
	assert(d1.day() == 4);

	d2 -= years(10); //2000-11-18
	assert(d2.year() == d1.year() + 7);

	{
		date d1(2017, 1, 1);

		date d2 = d1 + days(pos_infin);
		assert(d2.is_pos_infinity());

		d2 = d1 + days(not_a_date_time);
		assert(d2.is_not_a_date());
		d2 = date(neg_infin);
		days dd = d1 - d2;
		assert(dd.is_special() && !dd.is_negative());
	}

	{
		date d(2017, 3, 30);
		d -= months(1);
		d -= months(1);
		d += months(2);
		assert(d.day() == 31);
	}
}

日期与特殊日期长度、特殊日期与日期长度进行运算的结果也会是特殊日期:

date d1(2017, 1, 1);

date d2 = d1 + days(pos_infin);
assert(d2.is_pos_infinity());

d2 = d1 + days(not_a_date_time);
assert(d2.is_not_a_date());
d2 = date(neg_infin);
days dd = d1 - d2;
assert(dd.is_special() && !dd.is_negative());

在与months、years这两个时长类进行计算时要注意:如果日期是月末的最后一天,那么加减月或年会得到同样的月末时间,而不是简单的月份或者年份加1,这是合乎生活常识的。但当天数是月末的28、29时,如果加减月份到2月份,那么随后的运算就总是月末操作,原来的天数信息就会丢失。例如:

date d(2014, 3, 30);
d -= months(1); //2014-2-28,变为月末,原30的日期信息丢失
d -= months(1); //2014-1-31
d += months(2); //2014-3-31
assert(d.day() == 31); //与原来日期不相等

使用days则不会出现这样的问题,如果担心weeks、months、years这些时长类被无意使用进而扰乱了代码,可以undef宏BOOST_DATE_TIME_OPTIONAL_GREGORIAN_TYPES,这将使 date_time库不包含它们的定义头文件<boost/date_time/gregorian/greg_duration_types.hpp>

日期区间

date_time库使用date_period类来表示日期区间的概念,它是时间轴上的一个左闭右开区间,端点是两个date对象。区间的左边界必须小于右边界,否则date_period将表示一个无效的日期区间。

date_period的类摘要如下:

class date_period
{
public:
   period(date, date);
   period(date, days);

   date begin() const;
   date end() const;
   date last() const;
   days length() const;
   bool is_null() const;

   bool operator==(const period &) const;
   bool operator<(const period &) const;
  
   void shift(const days &);
   void expend(const days &);

   bool contains(const date &) const;
   bool contains(const period &) const;
   bool intersects(const period &) const;
   bool is_adjacent(const period &) const;
   bool is_before(const date &) const;
   bool is_after(const date &) const;
   period intersection(const period &) const;
   period merge(const period &) const;
   period span(const period &) const;
};

date_period可以指定区间的两个端点构造,也可以指定左端点再加上时长构造,通常后一种方法比较常用,相当于生活中从某天开始的一个周期。例如:

date_period dp1(date(2014,1,1), days(20));
date_period dp2(date(2014,1,1), date(2013,1,1)); //无效
date_period dp3(date(2014,3,1), days(-20)); //无效

成员函数begin()和 last()返回日期区间的两个端点,而end()返回 last()后的第一天,与标准容器中的end()含义相同,是一个“逾尾的位置”。length()返回日期区间的长度,以天为单位。如果日期区间在构造时使用了左大右小的端点或者日期长度是0,那么is_nul1()函数将返回true。例如:

date_period dp(date(2014,1,1), days(20));

assert(!dp.is_null());
assert(dp.begin().day() == 1);
assert(dp.last().day() == 20);
assert(dp.end().day() == 21);
assert(dp.length().days() == 20);

date_period可以进行全序比较运算,但比较不是依据日期区间的长度,而是依据区间的端点,即第一个区间的end()和第二个区间的begin(),判断两个区间在时间轴上的位置大小。如果两个日期区间相交或者包含,那么比较操作无意义。

date_period也支持输入输出操作符,默认的输入输出格式是一个[YYYY-mmm-DD/YYYY-mmm-DD]形式的字符串。例如:

date_period dp1(date(2014,1,1), days(20));
date_period dp2(date(2014,2,19), days(10));
cout << dp1; //[2014-Jan-01/2014-Jan-20]
assert(dp1 < dp2);

代码实例

#include <iostream>
using namespace std;

//#define DATE_TIME_NO_DEFAULT_CONSTRUCTOR
#include <boost/date_time/gregorian/gregorian.hpp>
using namespace boost::gregorian;

//

void case1()
{
	date d1;
	date d2(2010, 1, 1);
	date d3(2000, Jan, 1);
	date d4(d2);

	assert(d1 == date(not_a_date_time));
	assert(d2 == d4);
	assert(d3 < d4);
}

//

void case2()
{
	date d1 = from_string("1999-12-31");
	date d2(from_string("2015/1/1"));
	date d3 = from_undelimited_string("20011118");

	cout << d1 << d2 << d3 << endl;

	cout << day_clock::local_day() << endl;
	cout << day_clock::universal_day() << endl;

}

//
void case3()
{
	date d1(neg_infin);
	date d2(pos_infin);
	date d3(not_a_date_time);
	date d4(max_date_time);
	date d5(min_date_time);

	cout << d1 << d2 << d3 << d4 << d5 << endl;

	try
	{
		//date d1(1399,12,1);
		//date d2(10000,1,1);
		date d3(2017, 2, 29);
	}
	catch (std::exception& e)
	{
		cout << e.what() << endl;
	}
}

//
void case4()
{
	date d(2017, 6, 1);
	assert(d.year() == 2017);
	assert(d.month() == 6);
	assert(d.day() == 1);

	date::ymd_type ymd = d.year_month_day();
	assert(ymd.year == 2017);
	assert(ymd.month == 6);
	assert(ymd.day == 1);

	cout << d.day_of_week() << endl;
	cout << d.day_of_year() << endl;
	assert(d.end_of_month() == date(2017, 6, 30));

	cout << date(2015, 1, 10).week_number() << endl;
	cout << date(2016, 1, 10).week_number() << endl;
	cout << date(2017, 1, 10).week_number() << endl;

	assert(date(pos_infin).is_infinity());
	assert(date(pos_infin).is_pos_infinity());
	assert(date(neg_infin).is_neg_infinity());
	assert(date(not_a_date_time).is_not_a_date());
	assert(date(not_a_date_time).is_special());
	assert(!date(2017, 5, 31).is_special());


}

//
void case5()
{
	date d(2017, 1, 23);

	cout << to_simple_string(d) << endl;
	cout << to_iso_string(d) << endl;
	cout << to_iso_extended_string(d) << endl;
	cout << d << endl;

	//cout << "input date:";
	//cin >>d;
	//cout << d;

}

//
void case6()
{
	date d(2017, 5, 20);
	tm t = to_tm(d);
	assert(t.tm_hour == 0 && t.tm_min == 0);
	assert(t.tm_year == 117 && t.tm_mday == 20);

	date d2 = date_from_tm(t);
	assert(d == d2);

}

//
void case7()
{
	days dd1(10), dd2(-100), dd3(255);

	assert(dd1 > dd2 && dd1 < dd3);
	assert(dd1 + dd2 == days(-90));
	assert((dd1 + dd3).days() == 265);
	assert(dd3 / 5 == days(51));

	weeks w(3);
	assert(w.days() == 21);

	months m(5);
	years y(2);

	months m2 = y + m;
	assert(m2.number_of_months() == 29);
	assert((y * 2).number_of_years() == 4);

}

//
void case8()
{
	date d1(2000, 1, 1), d2(2017, 11, 18);
	cout << d2 - d1 << endl;
	assert(d1 + (d2 - d1) == d2);

	d1 += days(10);
	assert(d1.day() == 11);
	d1 += months(2);
	assert(d1.month() == 3 && d1.day() == 11);
	d1 -= weeks(1);
	assert(d1.day() == 4);

	d2 -= years(10);
	assert(d2.year() == d1.year() + 7);

	{
		date d1(2017, 1, 1);

		date d2 = d1 + days(pos_infin);
		assert(d2.is_pos_infinity());

		d2 = d1 + days(not_a_date_time);
		assert(d2.is_not_a_date());
		d2 = date(neg_infin);
		days dd = d1 - d2;
		assert(dd.is_special() && !dd.is_negative());
	}

	{
		date d(2017, 3, 30);
		d -= months(1);
		d -= months(1);
		d += months(2);
		assert(d.day() == 31);
	}
}

//
void case9()
{
	date_period dp1(date(2017, 1, 1), days(20));
	date_period dp2(date(2017, 1, 1), date(2016, 1, 1));
	date_period dp3(date(2017, 3, 1), days(-20));

	date_period dp(date(2017, 1, 1), days(20));

	assert(!dp.is_null());
	assert(dp.begin().day() == 1);
	assert(dp.last().day() == 20);
	assert(dp.end().day() == 21);
	assert(dp.length().days() == 20);

	{
		date_period dp1(date(2017, 1, 1), days(20));
		date_period dp2(date(2017, 2, 19), days(10));

		cout << dp1;                        //[2010-Jan-01/2010-Jan-20]
		assert(dp1 < dp2);
	}
}

//

int main()
{
	case1();
	case2();
	case3();
	case4();
	case5();
	case6();
	case7();
	case8();
	case9();
}

在这里插入图片描述

日期区间运算

date_period同date、 days一样,也支持很多运算。

成员函数shift()和expand()可以变动区间:shift()将日期区间平移n天而长度不变,expand()将日期区间向两端延伸n天,相当于区间长度加2n天。例如:

date_period dp(date(2017, 1, 1), days(20));

dp.shift(days(3));
assert(dp.begin().day() == 4);
assert(dp.length().days() == 20);

dp.expand(days(3));
assert(dp.begin().day() == 1);
assert(dp.length().days() == 26);

date_period还可以使用成员函数判断某个日期是否在区间内,或者计算日期区间的交集:

is_before()/is_after()     日期区间是否在日期前或后;
contains()                 日期区间是否包含另一个区间或者日期;
intersects()               两个日期区间是否存在交集;
intersection()             返回两个区间的交集,如果无交集返回一个无效区间;
is_adjacent()              两个日期区间是否相邻。

示范这几个成员函数用法的代码如下:

date_period dp(date(2010, 1, 1), days(20)); //1-1至1-20

assert(dp.is_after(date(2009, 12, 1)));
assert(dp.is_before(date(2010, 2, 1)));
assert(dp.contains(date(2010, 1, 10)));

date_period dp2(date(2010, 1, 5), days(10)); //1-5至1-15
assert(dp.contains(dp2));

assert(dp.intersects(dp2));
assert(dp.intersection(dp2) == dp2);

date_period dp3(date(2010, 1, 21), days(5)); //1-21至1-26
assert(!dp3.intersects(dp2));
assert(dp3.intersection(dp2).is_null());

assert(dp.is_adjacent(dp3));
assert(!dp.intersects(dp3));

date_period提供了两种并集操作。

merge()        返回两个区间的并集,如果区间无交集或者不相邻则返回无效区间
span()         合并两日期区间及两者间的间隔,相当于广义的merge()

示范这两个并集函数的用法和区别的代码如下:

date_period dp1(date(2010, 1, 1), days(20));
date_period dp2(date(2010, 1, 5), days(10));
date_period dp3(date(2010, 2, 1), days(5));
date_period dp4(date(2010, 1, 15), days(10));

assert(dp1.contains(dp2) && dp1.merge(dp2) == dp1);
assert(!dp1.intersects(dp3) && dp1.merge(dp3).is_null());
assert(dp1.intersects(dp2) && dp1.merge(dp4).end() == dp4.end());
assert(dp1.span(dp3).end() == dp3.end());

日期迭代器

date_time 库为日期处理提供了迭代器的概念,可以用简单的递增或者递减操作符连续访问日期,这些迭代器包括 day_iterator、week_iterator、month_iterator 和year_iterator,它们分别以天、周、月和年为单位增减。

日期迭代器的用法基本类似,都需要在构造时传入一个起始日期和增减步长(可以是一天、两周或者N个月等等,默认是1个单位),然后就可以用operator++operator--变化日期。迭代器相当于一个date对象的指针,可以用解引用操作符*获得迭代器当前的日期对象,也可以用->直接调用日期对象的成员函数。

为了方便用户使用,日期迭代器还重载了比较操作符,不需要用解引用操作符就可以直接与其他日期对象比较大小。

示范日期迭代器基本用法的代码如下:

date d(2007, 9, 28);
day_iterator d_iter(d); //增减步长默认为1天

assert(d_iter == d);
++d_iter; //递增1天
assert(d_iter == date(2007, 9, 29));

year_iterator y_iter(*d_iter, 10); //增减步长为10年
assert(y_iter == d + days(1));
++y_iter; //递增10年
assert(y_iter->year() == 2017);

需要提醒读者注意的是day_iterator、week_iterator虽然名字叫"迭代器",但它并不符合标准迭代器的定义,没有difference_type、pointer、reference等内部类型定义,不能使用std::advance()或者operator+=来前进或者后退,例如:

day_iterator iter(day_clock::local_day()); //声明一个日期迭代器
++iter; //使用operator++,正确

//iter += 5; //错误,编译失败
//std::advance(iter, 5); 错误,编译失败

其他功能

boost::gregorian::gregorian_calendar类提供了格里高利历的一些操作函数,基本上它被date类在内部使用,用户通常很少用到。但它也提供了几个有用的静态函数:成员函数is_leap_year()可以判断年份是否是闰年,end_of_month_day()给定年份和月份,返回该月的最后一天。例如:

typedef gregorian_calendar gre_cal; //typedef以简化代码书写
cout << "Y2017 is "
	<< (gre_cal::is_leap_year(2017) ? "" : "not")
	<< " a leap year." << endl;
assert(gre_cal::end_of_month_day(2017, 2) == 28);

date_time库还提供了很多有用的日期生成器,如某个月的最后一个星期天或者第一个星期一等等,它们封装了一些常用但计算起来又比较麻烦的时间概念。

综合运用

显示月历
首先我们实现一个打印月历的功能。用户指定一个日期,可以得到该月的起始和结束日期,然后我们构造一个日期迭代器,使用for循环打印出日期。

date d(2017, 1, 23); //实际运行时日期可以从cin获得

date d_start(d.year(), d.month(), 1); //当月第一天
date d_end = d.end_of_month(); //当月最后一天

for (day_iterator d_iter(d_start); //构造日期迭代器
	d_iter <= d_end; ++d_iter) //循环结束条件
{
	cout << *d_iter << " " << //输出日期和星期
		d_iter->day_of_week() << endl;
}

简单的日期计算
下面的程序计算一个人十八岁的生日是星期几,当月有几个星期天,当年有多少天:

date d(2017, 1, 23); //声明日期对象

date d18years = d + years(18); //加上18年
cout << d18years << " is "
	<< d18years.day_of_week() << endl;

int count = 0; //星期天的计数器
for (day_iterator d_iter(date(d18years.year(), 1, 1));
	d_iter <= d18years.end_of_month(); ++d_iter)
{
	if (d_iter->day_of_week() == Sunday) //是星期天则计数增加
	{
		++count;
	}
}
cout << "total " << count << " Sundays." << endl;

count = 0; //计数器归0
for (month_iterator m_iter(date(d18years.year(), 1, 1));
	m_iter < date(d18years.year() + 1, 1, 1); ++m_iter)
{
	count += m_iter->end_of_month().day(); //累加月份的天数
}
cout << "total " << count << " days of year." << endl;

其实计算当年的天数没有必要累加每月的天数,可以简单地只判断该年是否是闰年就可以了,例如:

cout << (gre_cal::is_leap_year(d18years.year())?365:366);

计算信用卡的免息期
最后我们实现一个较复杂的程序,计算信用卡的最长免息期。

先来简单了解一下信用卡免息期的计算规则:使用信用卡的当天称为消费日,信用卡每月有一个记账日,在记账日之后有一个固定的免息还款期限,通常为20天,因此每笔信用卡交易的免息期就是消费日到下一个记账日的时间再加上还款期限,最长可以达到50天。

我们使用类credit_card来代表信用卡,它保存了信用卡的基本信息,包括发卡银行名和记账日:

class credit_card
{
public:
	string bank_name; //银行名
	int bill_day_no; //记账日

	credit_card(const char* bname, int no) : //构造函数
		bank_name(bname), bill_day_no(no) {}

我们使用成员函数calc_free_days()来计算信用卡的免息期,它依据传入的消费日得到"下一个"记账日,并算出免息期:

int calc_free_days(date consume_day = day_clock::local_day()) const
{
	date bill_day(consume_day.year(), consume_day.month(), bill_day_no);
	if (consume_day > bill_day)
	{
		bill_day += months(1);
	}

	return (bill_day - consume_day).days() + 20;
}

为了支持比较操作,我们还需要为credit_card增加小于比较操作符重载:

friend bool operator<(const credit_card& l, const credit_card& r)
{
	return l.calc_free_days() < r.calc_free_days();
}
};

最后,我们在main()函数中创建两个信用卡对象,并使用标准库的算法std::max()来比较这两个信用卡,从而决定使用免息期最长的那张信用卡:

credit_card a("A bank", 25);
credit_card b("B bank", 12);

credit_card tmp = std::max(a, b);
cout << "You should use " << tmp.bank_name
	<< ", free days = " << tmp.calc_free_days() << endl;

通过这个程序,我们可以知道:如果今天是5日,那么应该使用a银行的卡,它的免息期是40天;如果今天是15日,那么应该使用B银行的卡,它的免息期是48天。

credit_card 的calc_free_days()函数还可以直接传入指定的日期,以计算任意时间点的免息期,例如:

cout<< a.calc_free_days(date(2010,5,26));

可以进一步改进这个程序,把credit_card放入标准容器,使用std::sort()算法来管理更多的信用卡(提示:使用函数对象或lambda表达式保存消费日期,作为比较谓词传递给算法)。

代码示例

#include <iostream>
using namespace std;

#include <boost/date_time/gregorian/gregorian.hpp>
using namespace boost::gregorian;

//
void case1()
{
	date_period dp(date(2017, 1, 1), days(20));

	dp.shift(days(3));
	assert(dp.begin().day() == 4);
	assert(dp.length().days() == 20);

	dp.expand(days(3));
	assert(dp.begin().day() == 1);
	assert(dp.length().days() == 26);

}

//

void case2()
{
	date_period dp(date(2010, 1, 1), days(20));

	assert(dp.is_after(date(2009, 12, 1)));
	assert(dp.is_before(date(2010, 2, 1)));
	assert(dp.contains(date(2010, 1, 10)));

	date_period dp2(date(2010, 1, 5), days(10));
	assert(dp.contains(dp2));

	assert(dp.intersects(dp2));
	assert(dp.intersection(dp2) == dp2);

	date_period dp3(date(2010, 1, 21), days(5));
	assert(!dp3.intersects(dp2));
	assert(dp3.intersection(dp2).is_null());

	assert(dp.is_adjacent(dp3));
	assert(!dp.intersects(dp3));

}

//
void case3()
{
	date_period dp1(date(2010, 1, 1), days(20));
	date_period dp2(date(2010, 1, 5), days(10));
	date_period dp3(date(2010, 2, 1), days(5));
	date_period dp4(date(2010, 1, 15), days(10));

	assert(dp1.contains(dp2) && dp1.merge(dp2) == dp1);
	assert(!dp1.intersects(dp3) && dp1.merge(dp3).is_null());
	assert(dp1.intersects(dp2) && dp1.merge(dp4).end() == dp4.end());
	assert(dp1.span(dp3).end() == dp3.end());
}

//
void case4()
{
	date d(2007, 9, 28);
	day_iterator d_iter(d);

	assert(d_iter == d);
	++d_iter;
	assert(d_iter == date(2007, 9, 29));

	year_iterator y_iter(*d_iter, 10);
	assert(y_iter == d + days(1));
	++y_iter;
	assert(y_iter->year() == 2017);

	day_iterator iter(day_clock::local_day());
	++iter;

	//iter += 5;
	//std::advance(iter, 5);
}

//
void case5()
{
	typedef gregorian_calendar gre_cal;
	cout << "Y2017 is "
		<< (gre_cal::is_leap_year(2017) ? "" : "not")
		<< " a leap year." << endl;
	assert(gre_cal::end_of_month_day(2017, 2) == 28);
}

//
void case6()
{
	date d(2017, 1, 23);

	date d_start(d.year(), d.month(), 1);
	date d_end = d.end_of_month();

	for (day_iterator d_iter(d_start);
		d_iter <= d_end; ++d_iter)
	{
		cout << *d_iter << " " <<
			d_iter->day_of_week() << endl;
	}

}

//
void case7()
{
	date d(2017, 1, 23);

	date d18years = d + years(18);
	cout << d18years << " is "
		<< d18years.day_of_week() << endl;

	int count = 0;
	for (day_iterator d_iter(date(d18years.year(), 1, 1));
		d_iter <= d18years.end_of_month(); ++d_iter)
	{
		if (d_iter->day_of_week() == Sunday)
		{
			++count;
		}
	}
	cout << "total " << count << " Sundays." << endl;

	count = 0;
	for (month_iterator m_iter(date(d18years.year(), 1, 1));
		m_iter < date(d18years.year() + 1, 1, 1); ++m_iter)
	{
		count += m_iter->end_of_month().day();
	}
	cout << "total " << count << " days of year." << endl;

}

//
class credit_card
{
public:
	string bank_name;
	int bill_day_no;

	credit_card(const char* bname, int no) :
		bank_name(bname), bill_day_no(no) {}

	int calc_free_days(date consume_day = day_clock::local_day()) const
	{
		date bill_day(consume_day.year(), consume_day.month(), bill_day_no);
		if (consume_day > bill_day)
		{
			bill_day += months(1);
		}

		return (bill_day - consume_day).days() + 20;
	}

	friend bool operator<(const credit_card& l, const credit_card& r)
	{

		return l.calc_free_days() < r.calc_free_days();
	}
};

void case8()
{
	credit_card a("A bank", 25);
	credit_card b("B bank", 12);

	credit_card tmp = std::max(a, b);
	cout << "You should use " << tmp.bank_name
		<< ", free days = " << tmp.calc_free_days() << endl;
}

//

int main()
{
	case1();
	case2();
	case3();
	case4();
	case5();
	case6();
	case7();
	case8();
}

在这里插入图片描述

编译配置宏

宏DATE_TIME_NO_DEFAULT_CONSTRUCTOR可以禁止编译器创造出date和ptime的缺省构造函数,强制它们在构造时必须有一个有效的值,可以避免某些疏忽而导致的错误。

宏 BOOST_DATE_TIME_OPTIONAL_GREGORIAN_TYPES 启用了weeks、months、years 等日期区间便捷类型,它们在处理日期时很有用,可以使代码更清晰易懂。但它们有时候也会在日期运算时产生非预期结果,如果不想使用它们,就 undef 这个宏,从而在程序中总使用days 保证代码的正确性。

宏 BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG将启用date_time库更高的时间精确度,由微秒变为纳秒,同时纳秒相关的一些函数和类也会启用。缺省情况下它是关闭的,因为纳秒精度通常很依赖于操作系统,而且实际生活中很少用到这么高的精确度。

date_time 库编译的对象是格里高利历源码,不使用纳秒来处理日期,因此BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG宏对库的编译没有任何影响。

自定义字面值

C++11 标准新增了自定义字面值的特性(C++11.2.14.8),所以我们可以编写operator""操作符来简化日期时间相关的代码。

下面的代码定义了三个字面值操作符:

days operator"" _D(unsigned long long n) //后缀_D,返回天
{
	return days(n);
}

weeks operator"" _W(unsigned long long n) //后缀_W,返回周
{
	return weeks(n);
}

date operator"" _YMD(const char* s, std::size_t) //年月日格式,返回日期
{
	return from_string(s);
}

int main()
{
	auto d = 100_D; //100天
	auto w = 1_W; //1周

	assert(d.days() == 100);
	assert(w.days() == (7_D).days()); //可以直接用字面值变量

	auto today = "2014/11/3"_YMD; //字符串转换日期
	cout << today << endl;
}

适当地使用自定义字面值特性可以很好地简化我们的代码,让代码更具可读性。

格式化时间

适当地使用自定义字面值特性可以很好地简化我们的代码,让代码更具可读性。date_time 库提供了专门的格式化对象date_facet、time_facet等来搭配IO流,定制日期时间的表现形式。

这些格式化对象就像是printf ()函数,使用一个格式化字符串来定制日期或时间的格式,也同样有大量的格式标志符。

示范格式化的使用、把日期格式化为中文显示的代码如下:

date d(2014, 11, 3);
date_facet* dfacet = new date_facet("%Y年%m月%d日");
cout.imbue(locale(cout.getloc(), dfacet));
cout << d << endl;

time_facet* tfacet = new time_facet("%Y年%m月%d日%H点%M分%S%F秒");
cout.imbue(locale(cout.getloc(), tfacet));
cout << ptime(d, hours(21) + minutes(50) + millisec(100)) << endl;

本地时间

之前对date_time库的讨论都基于简单的时间,通常对于日常工作和生活是足够的。但如果考虑到世界各地不同时区的因素,时间就变得复杂了,两个不同时区对同一个时间点的日期和时间表示可能会不同,如果再加上某些地区的夏令时因素就更加复杂了。

date_time库使用time_zone_base、posix_time_zone、custom_time_zone、local_date_time、c_local_adjustor等类和一个文本格式的时区数据库来解决本地时间中时区和夏令时的问题。

本地时间功能位于名字空间boost::local_time,为了使用本地时间功能,需要包含头文件<boost/date_time/local_time/local_time.hpp>,即:

#include <boost/date_time/local_time/local_time.hpp>
using namespace boost::local_time;

time_zone_base是时区表示的抽象类,通常我们使用一个 typedef:time_zone_ptr,它是一个指向time_zone_base的智能指针。

local_date_time是一个含有时区信息的时间对象,它可以由date+time_duration+时区构造,构造时必须指定这个时间是否是夏令时,本地时间在内部以uTc 的形式保存,以方便计算。

为了便于时区编程, date_time 库附带了一个小型csv格式的文本数据库date_time_zonespec.csv,位于libs/date_time/data/下,可以自由使用。这个数据库包含了世界上几乎所有国家和地区的时区信息,tz_database类专门管理这个数据库,只要指定时区名,就可以很方便地获得时区信息——一个.time_zone_ptr。

假设从北京飞往旧金山,飞行时间为12个小时,示范跨时区的时间转换的代码如下:

#include <boost/date_time/gregorian/gregorian.hpp>
using namespace boost::gregorian;
#include <boost/date_time/posix_time/posix_time.hpp>
using namespace boost::posix_time;
#include <boost/date_time/local_time/local_time.hpp>
using namespace boost::local_time;

int main()
{
	tz_database tz_db; //时区数据库对象
	//假设文本数据库位于当前目录下
	tz_db.load_from_file("./date_time_zonespec.csv"); 
    //使用字符串Asia/Shanghai获得上海时区,即北京时间
	time_zone_ptr shz = tz_db.time_zone_from_region("Asia/Shanghai");
    //使用字符串America/Los_Angeles获得旧金山所在的落基山时区
	time_zone_ptr sfz = tz_db.time_zone_from_region("America/Los_Angeles");

	cout << shz->has_dst() << endl; //上海时区无夏令时
	cout << shz->std_zone_name() << endl; //上海时区的名称CST

	local_date_time dt_bj(date(2014, 3, 6), //北京时间2014-3-6
		hours(16), //下午16点
		shz, //上海时区
		false); //没有夏令时
	cout << dt_bj << endl;

	time_duration flight_time = hours(12); //飞行12小时
	dt_bj += flight_time; //到达的北京时间
	cout << dt_bj << endl;
	local_date_time dt_sf = dt_bj.local_time_in(sfz); //旧金山当地时间
	cout << dt_sf;
}

序列化

date_time库可以使用boost.serialization库的能力实现数据序列化,把日期时间数据存入某个文件,之后在任意的时刻读取恢复,如同流操作一样简单方便。

代码示例

#include <iostream>
using namespace std;

//#define BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG
#include <boost/date_time/gregorian/gregorian.hpp>
using namespace boost::gregorian;
#include <boost/date_time/posix_time/posix_time.hpp>
using namespace boost::posix_time;

//

void case1()
{
	date d(2014, 11, 3);
	date_facet* dfacet = new date_facet("%Y年%m月%d日");
	cout.imbue(locale(cout.getloc(), dfacet));
	cout << d << endl;

	time_facet* tfacet = new time_facet("%Y年%m月%d日%H点%M分%S%F秒");
	cout.imbue(locale(cout.getloc(), tfacet));
	cout << ptime(d, hours(21) + minutes(50) + millisec(100)) << endl;

}

//
days operator"" _D(unsigned long long n)
{
	return days(n);
}

weeks operator"" _W(unsigned long long n)
{
	return weeks(n);
}

date operator"" _YMD(const char* s, std::size_t)
{
	return from_string(s);
}

void case2()
{
	auto d = 100_D;
	auto w = 1_W;

	assert(d.days() == 100);
	assert(w.days() == (7_D).days());

	auto today = "2014/11/3"_YMD;
	cout << today << endl;
}

//
#include <boost/date_time/local_time/local_time.hpp>
using namespace boost::local_time;

void case3()
{
	tz_database tz_db;
	tz_db.load_from_file("./date_time_zonespec.csv");

	time_zone_ptr shz = tz_db.time_zone_from_region("Asia/Shanghai");

	time_zone_ptr sfz = tz_db.time_zone_from_region("America/Los_Angeles");

	cout << shz->has_dst() << endl;
	cout << shz->std_zone_name() << endl;

	local_date_time dt_bj(date(2014, 3, 6),
		hours(16),
		shz,
		false);
	cout << dt_bj << endl;

	time_duration flight_time = hours(12);
	dt_bj += flight_time;
	cout << dt_bj << endl;
	local_date_time dt_sf = dt_bj.local_time_in(sfz);
	cout << dt_sf;
}

//

int main()
{
	case1();
	case2();
	case3();
}

在这里插入图片描述
date_time_zonespec.csv下载地址:
https://download.csdn.net/download/qq_36314864/88049099

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阳光开朗男孩

你的鼓励是我最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值