10.2. 历法日期

Boost.DateTime 只支持基于格里历的历法日期,这通常不成问题,因为这是最广泛使用的历法。 如果你与其它国家的某人有个会议,时间在2010年1月5日,你可以期望无需与对方确认这个日期是否基于格里历。

格里历是教皇 Gregory XIII 在1582年颁发的。 严格来说,Boost.DateTime 支持由1400年至9999年的历法日期,这意味着它支持1582年以前的日期。 因此,Boost.DateTime 可用于任一历法日期,只要该日期在转换为格里历后是在1400年之后。 如果需要更早的年份,就必须使用其它库来代替。

用于处理历法日期的类和函数位于名字空间 boost::gregorian 中,定义于 boost/date_time/gregorian/gregorian.hpp。 要创建一个日期,请使用 boost::gregorian::date 类。

  1. #include <boost/date_time/gregorian/gregorian.hpp>
  2. #include <iostream>
  3.  
  4. int main()
  5. {
  6. boost::gregorian::date d(2010, 1, 30);
  7. std::cout << d.year() << std::endl;
  8. std::cout << d.month() << std::endl;
  9. std::cout << d.day() << std::endl;
  10. std::cout << d.day_of_week() << std::endl;
  11. std::cout << d.end_of_month() << std::endl;
  12. }

boost::gregorian::date 提供了多个构造函数来进行日期的创建。 最基本的构造函数接受一个年份、一个月份和一个日期作为参数。 如果给定的是一个无效值,则将分别抛出 boost::gregorian::bad_year, boost::gregorian::bad_monthboost::gregorian::bad_day_of_month 类型的异常,这些异常均派生自 std::out_of_range

正如在这个例子中所示的, 有多个方法用于访问一个日期。 象 year(), month()day() 这些方法访问用于初始化的初始值,象 day_of_week()end_of_month() 这些方法则访问计算得到的值。

boost::gregorian::date 的构造函数则接受年份、月份和日期的值来设定一个日期,调用 month() 方法实际上会显示 Jan,而调用 day_of_week() 则显示 Sat。 它们不是普通的数字值,而分别是 boost::gregorian::date::month_typeboost::gregorian::date::day_of_week_type 类型的值。 不过,Boost.DateTime 为格式化的输入输出提供了全面的支持,可以将以上输出从 Jan 调整为 1

请留意,boost::gregorian::date 的缺省构造函数会创建一个无效的日期。 这样的无效日期也可以通过将 boost::date_time::not_a_date_time 作为单一参数传递给构造函数来显式地创建。

除了直接调用构造函数,也可以通过自由函数或其它对象的方法来创建一个 boost::gregorian::date 类型的对象。

  1. #include <boost/date_time/gregorian/gregorian.hpp>
  2. #include <iostream>
  3.  
  4. int main()
  5. {
  6. boost::gregorian::date d = boost::gregorian::day_clock::universal_day();
  7. std::cout << d.year() << std::endl;
  8. std::cout << d.month() << std::endl;
  9. std::cout << d.day() << std::endl;
  10.  
  11. d = boost::gregorian::date_from_iso_string("20100131");
  12. std::cout << d.year() << std::endl;
  13. std::cout << d.month() << std::endl;
  14. std::cout << d.day() << std::endl;
  15. }

这个例子使用了 boost::gregorian::day_clock 类,它是一个返回当前日期的时钟类。 方法 universal_day() 返回一个与时区及夏时制无关的 UTC 日期。 UTC 是世界时(universal time)的国际缩写。 boost::gregorian::day_clock 还提供了另一个方法 local_day(),它接受本地设置。 要取出本地时区的当前日期,必须使用 local_day()

名字空间 boost::gregorian 中包含了许多其它自由函数,将保存在字符串中的日期转换为 boost::gregorian::date 类型的对象。 这个例子实际上是通过 boost::gregorian::date_from_iso_string() 函数对一个以 ISO 8601 格式给出的日期进行转换。 还有其它相类似的函数,如 boost::gregorian::from_simple_string()boost::gregorian::from_us_string()

boost::gregorian::date 表示的是一个特定的时间点,而 boost::gregorian::date_duration 则表示了一段时间。

  1. #include <boost/date_time/gregorian/gregorian.hpp>
  2. #include <iostream>
  3.  
  4. int main()
  5. {
  6. boost::gregorian::date d1(2008, 1, 31);
  7. boost::gregorian::date d2(2008, 8, 31);
  8. boost::gregorian::date_duration dd = d2 - d1;
  9. std::cout << dd.days() << std::endl;
  10. }

由于 boost::gregorian::date 重载了 operator-() 操作符,所以两个时间点可以如上所示那样相减。 返回值的类型为 boost::gregorian::date_duration,表示了两个日期之间的时间长度。

boost::gregorian::date_duration 所提供的最重要的方法是 days(),它返回一段时间内所包含的天数。

我们也可以通过传递一个天数作为构造函数的唯一参数,来显式创建 boost::gregorian::date_duration 类型的对象。 要创建涉及星期数、月份数或年数的时间段,可以相应使用 boost::gregorian::weeks, boost::gregorian::monthsboost::gregorian::years

  1. #include <boost/date_time/gregorian/gregorian.hpp>
  2. #include <iostream>
  3.  
  4. int main()
  5. {
  6. boost::gregorian::date_duration dd(4);
  7. std::cout << dd.days() << std::endl;
  8. boost::gregorian::weeks ws(4);
  9. std::cout << ws.days() << std::endl;
  10. boost::gregorian::months ms(4);
  11. std::cout << ms.number_of_months() << std::endl;
  12. boost::gregorian::years ys(4);
  13. std::cout << ys.number_of_years() << std::endl;
  14. }

boost::gregorian::monthsboost::gregorian::years 都无法确定其天数,因为某月或某年所含天数是可长的。 不过,这些类的用法还是可以从以下例子中看出。

  1. #include <boost/date_time/gregorian/gregorian.hpp>
  2. #include <iostream>
  3.  
  4. int main()
  5. {
  6. boost::gregorian::date d(2009, 1, 31);
  7. boost::gregorian::months ms(1);
  8. boost::gregorian::date d2 = d + ms;
  9. std::cout << d2 << std::endl;
  10. boost::gregorian::date d3 = d2 - ms;
  11. std::cout << d3 << std::endl;
  12. }

该程序将一个月加到给定的日期 January 31, 2009 上,得到 d2,其为 February 28, 2009。 接着,再减回一个月得到 d3,又重新变回 January 31, 2009。 如上所示,时间点和时间长度可用于计算。 不过,需要考虑具体的情况。 例如,从某月的最后一天开始计算,boost::gregorian::months 总是会到达另一个月的最后一天,如果反复前后跳,就可能得到令人惊讶的结果。

  1. #include <boost/date_time/gregorian/gregorian.hpp>
  2. #include <iostream>
  3.  
  4. int main()
  5. {
  6. boost::gregorian::date d(2009, 1, 30);
  7. boost::gregorian::months ms(1);
  8. boost::gregorian::date d2 = d + ms;
  9. std::cout << d2 << std::endl;
  10. boost::gregorian::date d3 = d2 - ms;
  11. std::cout << d3 << std::endl;
  12. }

这个例子与前一个例子的不同之处在于,初始的日期是 January 30, 2009。 虽然这不是 January 的最后一天,但是向前跳一个月后得到的 d2 还是 February 28, 2009,因为没有 February 30 这一天。 不过,当我们再往回跳一个月,这次得到的 d3 就变成 January 31, 2009! 因为 February 28, 2009 是当月的最后一天,往回跳实际上是返回到 January 的最后一天。

如果你觉得这种行为过于混乱,可以通过取消 BOOST_DATE_TIME_OPTIONAL_GREGORIAN_TYPES 宏的定义来改变这种行为。 取消该宏后,boost::gregorian::weeks, boost::gregorian::monthsboost::gregorian::years 类都不再可用。 唯一剩下的类是 boost::gregorian::date_duration,只能指定前向或后向的跳过的天数,这样就不会再有意外的结果了。

boost::gregorian::date_duration 表示的是时间长度,而 boost::gregorian::date_period 则提供了对两个日期之间区间的支持。

  1. #include <boost/date_time/gregorian/gregorian.hpp>
  2. #include <iostream>
  3.  
  4. int main()
  5. {
  6. boost::gregorian::date d1(2009, 1, 30);
  7. boost::gregorian::date d2(2009, 10, 31);
  8. boost::gregorian::date_period dp(d1, d2);
  9. boost::gregorian::date_duration dd = dp.length();
  10. std::cout << dd.days() << std::endl;
  11. }

两个类型为 boost::gregorian::date 的参数指定了开始和结束的日期,它们被传递给 boost::gregorian::date_period 的构造函数。 此外,也可以指定一个开始日期和一个类型为 boost::gregorian::date_duration 的时间长度。 请注意,结束日期的前一天才是这个时间区间的最后一天,这对于理解以下例子的输出非常重要。

  1. #include <boost/date_time/gregorian/gregorian.hpp>
  2. #include <iostream>
  3.  
  4. int main()
  5. {
  6. boost::gregorian::date d1(2009, 1, 30);
  7. boost::gregorian::date d2(2009, 10, 31);
  8. boost::gregorian::date_period dp(d1, d2);
  9. std::cout << dp.contains(d1) << std::endl;
  10. std::cout << dp.contains(d2) << std::endl;
  11. }

这个程序用 contains() 方法来检查某个给定的日期是否包含在时间区间内。 虽然 d1d2 都是被传递给 boost::gregorian::dateperiod 的构造函数的,但是 contains() 仅对第一个返回 true。 因为结束日期不是区间的一部分,所以以 _d2 调用 contains() 会返回 false

boost::gregorian::date_period 还提供了其它方法,如移动一个区间,或计算两个重叠区间的交集。

除了日期类、时间长度类和时间区间类,Boost.DateTime 还提供了迭代器和其它有用的自由函数,如下例所示。

  1. #include <boost/date_time/gregorian/gregorian.hpp>
  2. #include <iostream>
  3.  
  4. int main()
  5. {
  6. boost::gregorian::date d(2009, 1, 5);
  7. boost::gregorian::day_iterator it(d);
  8. std::cout << *++it << std::endl;
  9. std::cout << boost::date_time::next_weekday(*it, boost::gregorian::greg_weekday(boost::date_time::Friday)) << std::endl;
  10. }

为了从一个指定日期向前或向后一天一天地跳,可以使用迭代器 boost::gregorian::day_iterator。 还有 boost::gregorian::week_iterator, boost::gregorian::month_iteratorboost::gregorian::year_iterator 分别提供了按周、按月或是按年跳的迭代器。

这个例子还使用了 boost::date_time::next_weekday(),它基于一个给定的日期返回下一个星期几的日期。 以下程序将显示 2009-Jan-09,因为它是 January 6, 2009 之的第一个Friday。