The Boost C++ Libraries

Calendar Dates

Boost.DateTime only supports calendar dates based on the Gregorian calendar, which in general is not a problem since this is the most widely used calendar. If you arrange a meeting with someone for May 12, 2014, you don’t need to say that the date is based on the Gregorian calendar.

The Gregorian calendar was introduced by Pope Gregory XIII in 1582. Boost.DateTime supports calendar dates for the years 1400 to 9999, which means that support goes back before the year 1582. Thus, you can use Boost.DateTime for any calendar date after the year 1400 as long as that date is converted to the Gregorian calendar. To process earlier dates, Boost.DateTime has to be extended by a new calendar.

The header file boost/date_time/gregorian/gregorian.hpp contains definitions for all classes and functions that process calendar dates. These functions and classes can be found in the namespace boost::gregorian. To create a date, use the class boost::gregorian::date.

Example 36.1. Creating a date with boost::gregorian::date
#include <boost/date_time/gregorian/gregorian.hpp>
#include <iostream>

int main()
{
  boost::gregorian::date d{2014, 1, 31};
  std::cout << d.year() << '\n';
  std::cout << d.month() << '\n';
  std::cout << d.day() << '\n';
  std::cout << d.day_of_week() << '\n';
  std::cout << d.end_of_month() << '\n';
}

boost::gregorian::date provides several constructors to create dates. The most basic constructor takes a year, a month, and a day as parameters. If an invalid value is given, an exception will be thrown of type boost::gregorian::bad_day_of_month, boost::gregorian::bad_year, or boost::gregorian::bad_month. All of these classes are derived from std::out_of_range.

As shown in Example 36.1, there are many member functions available. Some member functions, such as year(), month(), and day(), return the respective parts of a date, and others, such as day_of_week() and end_of_month(), calculate values.

The constructor of boost::gregorian::date expects numeric values for year, month, and day to set a date. However, the output of the sample program is Jan for the month and Fri for the day of the week. The return values of month() and day_of_week() are not regular numeric values, but values of type boost::gregorian::date::month_type and boost::gregorian::date::day_of_week_type. Boost.DateTime provides comprehensive support for formatted input and output, so it is possible to adjust the output from, for example, Jan to 1.

The default constructor of boost::gregorian::date creates an invalid date. An invalid date can also be created explicitly by passing boost::date_time::not_a_date_time as the sole parameter to the constructor.

Besides calling a constructor directly, an object of type boost::gregorian::date can be created via free-standing functions and member functions of other classes.

Example 36.2. Getting a date from a clock or a string
#include <boost/date_time/gregorian/gregorian.hpp>
#include <iostream>

using namespace boost::gregorian;

int main()
{
  date d = day_clock::universal_day();
  std::cout << d.year() << '\n';
  std::cout << d.month() << '\n';
  std::cout << d.day() << '\n';

  d = date_from_iso_string("20140131");
  std::cout << d.year() << '\n';
  std::cout << d.month() << '\n';
  std::cout << d.day() << '\n';
}

Example 36.2 uses the class boost::gregorian::day_clock, which returns the current date. The member function universal_day() returns a UTC date, which is independent of time zones and daylight savings. UTC is the international abbreviation for the universal time. boost::gregorian::day_clock also provides a member function called local_day(), which takes local settings into account. To retrieve the current date within the local time zone, use local_day().

The namespace boost::gregorian contains free-standing functions to convert a date stored as a string into an object of type boost::gregorian::date. Example 36.2 converts a date in the ISO 8601 format using the function boost::gregorian::date_from_iso_string(). Other functions include: boost::gregorian::from_simple_string() and boost::gregorian::from_us_string().

While boost::gregorian::date marks a specific time, boost::gregorian::date_duration denotes a duration.

Example 36.3. Using boost::gregorian::date_duration
#include <boost/date_time/gregorian/gregorian.hpp>
#include <iostream>

using namespace boost::gregorian;

int main()
{
  date d1{2014, 1, 31};
  date d2{2014, 2, 28};
  date_duration dd = d2 - d1;
  std::cout << dd.days() << '\n';
}

Because boost::gregorian::date overloads operator-, two points in time can be subtracted (see Example 36.3). The return value is of type boost::gregorian::date_duration and marks the duration between the two dates.

The most important member function offered by boost::gregorian::date_duration is days(), which returns the number of days in the duration specified.

Example 36.4. Specialized durations
#include <boost/date_time/gregorian/gregorian.hpp>
#include <iostream>

using namespace boost::gregorian;

int main()
{
  date_duration dd{4};
  std::cout << dd.days() << '\n';
  weeks ws{4};
  std::cout << ws.days() << '\n';
  months ms{4};
  std::cout << ms.number_of_months() << '\n';
  years ys{4};
  std::cout << ys.number_of_years() << '\n';
}

Objects of type boost::gregorian::date_duration can also be created by passing the number of days as a single parameter to the constructor. To create a duration that involves weeks, months, or years, use boost::gregorian::weeks, boost::gregorian::months, or boost::gregorian::years (see Example 36.4).

Neither boost::gregorian::months nor boost::gregorian::years will allow you to determine the number of days, because months and years vary in length. Nonetheless, using these classes can still make sense, as shown in Example 36.5.

Example 36.5. Processing specialized durations
#include <boost/date_time/gregorian/gregorian.hpp>
#include <iostream>

using namespace boost::gregorian;

int main()
{
  date d{2014, 1, 31};
  months ms{1};
  date d2 = d + ms;
  std::cout << d2 << '\n';
  date d3 = d2 - ms;
  std::cout << d3 << '\n';
}

Example 36.5 adds one month to the given date of January 31, 2014, which results in d2 being February 28, 2014. In the next step, one month is subtracted and d3 becomes January 31, 2014, again. As shown, points in time as well as durations can be used in calculations. However, some specifics need to be taken into account. For example, starting at the last day of a month, boost::gregorian::months always arrives at the last day of another month, which can lead to surprises.

Example 36.6. Surprises when processing specialized durations
#include <boost/date_time/gregorian/gregorian.hpp>
#include <iostream>

using namespace boost::gregorian;

int main()
{
  date d{2014, 1, 30};
  months ms{1};
  date d2 = d + ms;
  std::cout << d2 << '\n';
  date d3 = d2 - ms;
  std::cout << d3 << '\n';
}

Example 36.6 is identical to the previous one, except d is initialized to be January 30, 2014. Even though this is not the last day in January, jumping forward by one month results in d2 becoming February 28, 2014, because there is no February 30. However, jumping backwards by one month again results in d3 becoming January 31, 2014. Since February 28, 2014, is the last day in February, jumping backwards returns to the last day in January.

To change this behavior, undefine the macro BOOST_DATE_TIME_OPTIONAL_GREGORIAN_TYPES. After this macro is undefined, the classes boost::gregorian::weeks, boost::gregorian::months, and boost::gregorian::years will no longer be available. The only class still available will be boost::gregorian::date_duration, which simply jumps forwards and backwards by a specified number of days and does not give special consideration to the first and last day of the month.

Example 36.7. Using boost::gregorian::date_period
#include <boost/date_time/gregorian/gregorian.hpp>
#include <iostream>

using namespace boost::gregorian;

int main()
{
  date d1{2014, 1, 1};
  date d2{2014, 2, 28};
  date_period dp{d1, d2};
  date_duration dd = dp.length();
  std::cout << dd.days() << '\n';
}

While boost::gregorian::date_duration only works with durations, boost::gregorian::date_period supports ranges between two dates.

The constructor of boost::gregorian::date_period can accept two kinds of input. You can pass two parameters of type boost::gregorian::date, one for the beginning date and one for the end date. Or you can specify the beginning date and a duration of type boost::gregorian::date_duration. Please note that the day before the end date is actually the last day of the period. This is important in order to understand the output of Example 36.8.

Example 36.8. Testing whether a period contains dates
#include <boost/date_time/gregorian/gregorian.hpp>
#include <iostream>

using namespace boost::gregorian;

int main()
{
  date d1{2014, 1, 1};
  date d2{2014, 2, 28};
  date_period dp{d1, d2};
  std::cout.setf(std::ios::boolalpha);
  std::cout << dp.contains(d1) << '\n';
  std::cout << dp.contains(d2) << '\n';
}

Example 36.8 checks whether a specific date is within a period by calling contains(). Notice that although d2 defines the end of the period, it is not considered part of the period. Therefore, the member function contains() will return true when called with d1 and false when called with d2.

boost::gregorian::date_period provides additional member functions for operations such as shifting a period or calculating the intersection of two overlapping periods.

Boost.DateTime also provides iterators and other useful free-standing functions as shown in Example 36.9.

Example 36.9. Iterating over dates
#include <boost/date_time/gregorian/gregorian.hpp>
#include <iostream>

using namespace boost;

int main()
{
  gregorian::date d{2014, 5, 12};
  gregorian::day_iterator it{d};
  std::cout << *++it << '\n';
  std::cout << date_time::next_weekday(*it,
    gregorian::greg_weekday(date_time::Friday)) << '\n';
}

Use the iterator boost::gregorian::day_iterator to jump forward or backward by a day from a specific date. Use boost::gregorian::week_iterator, boost::gregorian::month_iterator, and boost::gregorian::year_iterator to jump by weeks, months, or years, respectively.

Example 36.9 also uses the function boost::date_time::next_weekday(), which returns the date of the next weekday based on a given date. Example 36.9 displays 2014-May-16, which is the first Friday following May 13, 2014.

Exercises

  1. Create a program that outputs the weekdays for next December 24 and the following two public holidays.

  2. Calculate your age in days. Your programm should automatically use today’s date.