Date and time calculations with Boost Date Time
Date and time calculations are important in many software applications, yet C++03 had limited support for manipulating dates and performing calculations with them. The Boost Date Time
library provides a set of intuitive interfaces for representing dates, timestamps, durations, and time intervals. By allowing simple arithmetic operations involving dates, timestamps, durations, and supplementing them with a set of useful date/time algorithms, it enables fairly sophisticated time and calendar calculations using little code.
Dates from the Gregorian calendar
The Gregorian calendar, also known as the Christian calendar, was introduced by Pope Gregory XIII in February 1582 and over the next few centuries, replaced the Julian calendar in the vast majority of the western world. The Date_Time
library provides a set of types for representing dates and related quantities:
boost::gregorian::date
: We use this type to represent a date in the Gregorian calendar.boost::gregorian::date_duration
: In addition to dates, we also need to represent durations—the length of time between two given dates in the calendar—in the unit of days. For this, we use the typeboost::gregorian::date_duration
. It refers to the same type asboost::gregorian::days
.boost::date_period
: A fixed date period of the calendar starting at a given date and extending for a specific duration is represented using the typeboost::date_period
.
Creating date objects
We can create objects of type boost::gregorian::date
using constituent parts of a date, namely the year, month, and day of the month. In addition, there are a number of factory functions that parse date strings in different formats to create objects of date
. In the following example, we illustrate the different ways of creating date
objects:
Listing 8.1: Using boost::gregorian::date
1 #include <boost/date_time.hpp> 2 #include <iostream> 3 #include <cassert> 4 namespace greg = boost::gregorian; 5 6 int main() { 7 greg::date d0; // default constructed, is not a date 8 assert(d0.is_not_a_date()); 9 // Construct dates from parts 10 greg::date d1(1948, greg::Jan, 30); 11 greg::date d2(1968, greg::Apr, 4); 12 13 // Construct dates from string representations 14 greg::date dw1 = greg::from_uk_string("15/10/1948"); 15 greg::date dw2 = greg::from_simple_string("1956-10-29"); 16 greg::date dw3 = greg::from_undelimited_string("19670605"); 17 greg::date dw4 = greg::from_us_string("10-06-1973"); 18 19 // Current date 20 greg::date today = greg::day_clock::local_day(); 21 greg::date londonToday = greg::day_clock::universal_day(); 22 23 // Take dates apart 24 std::cout << today.day_of_week() << " " << today.day() << ", " 25 << today.month() << ", " << today.year() << '\n'; 26 }
A default-constructed date represents an invalid date (line 7); the is_not_a_date
member predicate returns true for such dates (line 8). We can construct dates from its constituent parts: year, month, and day. Months can be indicated using enum
values named Jan
, Feb
, Mar
, Apr
, May
, Jun
, Jul
, Aug
, Sep
, Oct
, Nov
, and Dec
, which are abbreviated English names of the months of the year. Using special factory functions, dates can be constructed from other standard representations. We use the boost::gregorian::from_uk_string
function to construct a date
object from a string in the DD/MM/YYYY format, which is standard in UK (line 14). The boost::gregorian::from_us_string
function is used to construct a date
from a string in the MM/DD/YYYY format used in the US (line 17). The function boost::gregorian::from_simple_string
is used to construct a date
from a string in the ISO 8601 YYYY-MM-DD format (line 15), and its undelimited form YYYYMMDD can be converted into a date
object, using the boost::gregorian::from_undelimited_string
function (line 16).
Clocks provide a way to retrieve the current date and time on a system. Boost provides a couple of clocks for this purpose. The day_clock
type provides the local_day
(line 20) and universal_day
(line 21) functions, which return the current date in the local and UTC time zones, which could be same or differ by a day, depending on the time zone and time of the day.
Using convenient accessor member functions like day
, month
, year
, and day_of_week
, we can get at parts of a date
(lines 24-25).
Note
The Date_Time
library is not a header-only library, and in order to run examples in this section, they must be linked to the libboost_date_time
library. On Unix, with g++, you can use the following command line to compile and link examples involving Boost Date Time:
$ g++ example.cpp -o example -lboost_date_time
See Chapter 1, Introducing Boost, for more details.
Handling date durations
The duration of time between two dates is represented by boost::gregorian::date_duration
. In the following example, we compute time durations between dates, and add durations to dates or subtract durations from dates to derive new dates:
Listing 8.2: Basic date arithmetic
1 #include <boost/date_time.hpp> 2 #include <iostream> 3 namespace greg = boost::gregorian; 4 5 int main() { 6 greg::date d1(1948, greg::Jan, 30); 7 greg::date d2(1968, greg::Apr, 4); 8 9 greg::date_duration day_diff = d2 - d1; 10 std::cout << day_diff.days() 11 << " days between the two dates\n"; 12 13 greg::date six_weeks_post_d1 = d1 + greg::weeks(6); 14 std::cout << six_weeks_post_d1 << '\n'; 15 16 greg::date day_before_d2 = d2 - greg::days(1); 17 std::cout << day_before_d2 << '\n'; 18 }
We compute durations (which can be negative) as the difference of two dates (line 9), and print it in the unit of days (line 10). The date_duration
object internally represents durations in unit of days. We can also use the types boost::gregorian::weeks
, boost::gregorian::months
, and boost::gregorian::years
to construct date_duration
objects in units of weeks, months, or years. Note that boost::gregorian::days
and boost::gregorian::date_duration
refer to the same types. We get new dates by adding durations to or subtracting them from dates (lines 13, 16).
Date periods
A period starting at a fixed date is represented by the type boost::gregorian::date_period
. In the following example, we construct two date periods, a calendar year, and a US fiscal year. We calculate their overlap period, and then determine the date of the last Friday of each month in the overlapping period.
Listing 8.3: Date periods and calendar calculations
1 #include <boost/date_time.hpp> 2 #include <iostream> 3 namespace greg = boost::gregorian; 4 namespace dt = boost::date_time; 5 6 int main() { 7 greg::date startCal(2015, greg::Jan, 1); 8 greg::date endCal(2015, greg::Dec, 31); 9 10 greg::date startFiscal(2014, greg::Oct, 1); 11 greg::date endFiscal(2015, greg::Sep, 30); 12 13 greg::date_period cal(startCal, endCal); 14 greg::date_period fisc(startFiscal, endFiscal); 15 16 std::cout << "Fiscal year begins " << fisc.begin() 17 << " and ends " << fisc.end() << '\n'; 18 19 if (cal.intersects(fisc)) { 20 auto overlap = cal.intersection(fisc); 21 greg::month_iterator miter(overlap.begin()); 22 23 while (*miter < overlap.end()) { 24 greg::last_day_of_the_week_in_month 25 last_weekday(greg::Friday, miter->month()); 26 std::cout << last_weekday.get_date(miter->year()) 27 << '\n'; 28 ++miter; 29 } 30 } 31 }
We define date periods in terms of a start and an end date (lines 13, 14). We can check whether two periods overlap using the intersects
member function of date_period
(line 19), and obtain the overlap period using the intersection
member function (line 20). We iterate over a period by creating a month_iterator
at the start date (line 21) and iterating till the end date (line 23) using the preincrement operator (line 28). There are different kinds of iterators with different periods of iteration. We use boost::gregorian::month_iterator
to iterate over successive months in the period. The month_iterator
advances the date by a month, each time it is incremented. You can also use other iterators like year_iterator
, week_iterator
, and day_iterator
, which increment the iterator by a year, a week, or a day at a time.
For each month in the period, we want to find the date of the last Friday in that month. The Date Time
library has some interesting algorithm classes for calendar calculations of this sort. We use the boost::gregorian::last_day_of_the_week_in_month
algorithm for performing such calculations as the date of the last Friday of a month. We construct an object of last_day_of_the_week_in_month
, the constructor arguments being the day of the week (Friday) and the month (lines 24, 25). We then call its get_date
member function, passing to it the particular year for which we want the date (line 26).
Posix time
The Date_Time
library also provides a set of types for representing time points, durations, and periods.
boost::posix_time::ptime
: A specific point in time, or a time point, is represented by the typeboost::posix_time::ptime
.boost::posix_time::time_duration
: Like date durations, the length of time between two time points is called a time duration and is represented by the typeboost::posix_time::time_duration
.boost::posix_time::time_period
: A fixed interval starting at a specific time point and ending at another is called a time period and is represented by the typeboost::posix_time::time_period
.
These types and the operations on them together define a time system. Posix Time uses boost::gregorian::date
to represent the date part of time points.
Constructing time points and durations
We can create an instance of boost::posix_time::ptime
from its constituent parts, that is, date, hours, minutes, seconds, and so on or use factory functions that parse timestamp strings. In the following example, we show different ways in which we can create ptime
objects:
Listing 8.4: Using boost::posix_time
1 #include <boost/date_time.hpp> 2 #include <iostream> 3 #include <cassert> 4 #include <ctime> 5 namespace greg = boost::gregorian; 6 namespace pt = boost::posix_time; 7 8 int main() { 9 pt::ptime pt; // default constructed, is not a time 10 assert(pt.is_not_a_date_time()); 11 12 // Get current time 13 pt::ptime now1 = pt::second_clock::universal_time(); 14 pt::ptime now2 = pt::from_time_t(std::time(0)); 15 16 // Construct from strings 17 // Create time points using durations 18 pt::ptime pt1(greg::day_clock::universal_day(), 19 pt::hours(10) + pt::minutes(42) 20 + pt::seconds(20) + pt::microseconds(30)); 21 std::cout << pt1 << '\n'; 22 23 // Compute durations 24 pt::time_duration dur = now1 - pt1; 25 std::cout << dur << '\n'; 26 std::cout << dur.total_microseconds() << '\n'; 27 28 pt::ptime pt2(greg::day_clock::universal_day()), 29 pt3 = pt::time_from_string("2015-01-28 10:00:31.83"), 30 pt4 = pt::from_iso_string("20150128T151200"); 31 32 std::cout << pt2 << '\n' << to_iso_string(pt3) << '\n' 33 << to_simple_string(pt4) << '\n'; 34 }
Just as with date objects, a default-constructed ptime
object (line 9) is not a valid time point (line 10). There are clocks that can be used to derive the current time of the day, for example, second_clock
and microsec_clock
, which give the time with second or microsecond units. Calling the local_time
and universal_time
functions (line 13) on these clocks returns the current date and time in the local and UTC time zones respectively.
The from_time_t
factory function is passed the Unix time, which is the number of seconds elapsed since the Unix epoch (January 1, 1970 00:00:00 UTC), and constructs a ptime
object representing that point in time (line 14). The C library function time
, when passed 0, returns the current Unix time in UTC time zone.
The duration between two time points, which can be negative, is computed as the difference between two time points (line 24). It can be streamed to an output stream for printing the duration, by default, in terms of hours, minutes, seconds, and fractional seconds. Using accessor functions hours
, minutes
, seconds
, and fractional_seconds
, we can get the relevant parts of a duration. Or we can convert the entire duration to a second or subsecond unit using the accessors total_seconds
, total_milliseconds
, total_microseconds
, and total_nanoseconds
(line 26).
We can create a ptime
object from a Gregorian date and a duration of type boost::posix_time::time_duration
(lines 18-20). We can use the shim types hours
, minutes
, seconds
, microseconds
, and so on in the boost::posix_time
namespace to generate durations of type boost::posix_time::time_duration
in appropriate units and combine them using operator+
.
We can construct a ptime
object from just a boost::gregorian::date
object (line 28). This represents the time at midnight on the given date. We can use factory functions to create ptime
objects from different string representations (lines 29-30). The function time_from_string
is used to construct an instance of ptime
from a timestamp string in "YYYY-MM-DD hh:mm:ss.xxx…" format, in which the date and time parts are separated by a whitespace (line 29). The function from_iso_string
is used to construct a ptime
instance from a non-delimited string in the "YYYYMMDDThhmmss.xxx…" format, where an uppercase T separates the date and time parts (line 30). In both cases, the minutes, seconds, and fractional seconds are optional and are taken to be zero if not specified. The fractional seconds can follow the seconds, separated by a decimal point. These formats are locale dependent. For example, in several European locales, a comma is used instead of the decimal point.
We can stream ptime
objects to output streams like std::cout
(line 32). We can also convert ptime
instances to string
using conversion functions like to_simple_string
and to_iso_string
(lines 32-33). In English locales, the to_simple_string
function converts it to the "YYYY-MM-DD hh:mm:ss.xxx…" format. Notice that this is the same format expected by time_from_string
and is also the format used when ptime
is streamed. The to_iso_string
function converts it to the "YYYYMMDDThhmmss.xxx…" format, same as that expected by from_iso_string
.
Resolution
The smallest duration that can be represented using a time system is called its resolution. The precision with which time can be represented on a particular system, and therefore, the number of digits of the fractional seconds that are significant, depends on the resolution of the time system. The default resolution used by Posix Time is microsecond (10-6 seconds), that is, it cannot represent durations shorter than a microsecond and therefore cannot differentiate between two time points less than a microsecond apart. The following example demonstrates how to obtain and interpret the resolution of a time system:
Listing 8.5: Time ticks and resolution
1 #include <boost/date_time.hpp> 2 #include <iostream> 3 namespace pt = boost::posix_time; 4 namespace dt = boost::date_time; 5 6 int main() { 7 switch (pt::time_duration::resolution()) { 8 case dt::time_resolutions::sec: 9 std::cout << " second\n"; 10 break; 11 case dt::time_resolutions::tenth: 12 std::cout << " tenth\n"; 13 break; 14 case dt::time_resolutions::hundredth: 15 std::cout << " hundredth\n"; 16 break; 17 case dt::time_resolutions::milli: 18 std::cout << " milli\n"; 19 break; 20 case dt::time_resolutions::ten_thousandth: 21 std::cout << " ten_thousandth\n"; 22 break; 23 case dt::time_resolutions::micro: 24 std::cout << " micro\n"; 25 break; 26 case dt::time_resolutions::nano: 27 std::cout << " nano\n"; 28 break; 29 default: 30 std::cout << " unknown\n"; 31 break; 32 } 33 std::cout << pt::time_duration::num_fractional_digits() 34 << '\n'; 35 std::cout << pt::time_duration::ticks_per_second() 36 << '\n'; 37 }
The resolution
static function of the time_duration
class returns the resolution as an enumerated constant (line 7); we interpret this enum
and print a string to indicate the resolution (lines 7-32).
The num_fractional_digits
static function returns the number of significant digits of the fractional second (line 33); on a system with microsecond resolution, this would be 6, and on a system with nanosecond resolution, this would be 9. The ticks_per_second
static function converts 1 second to the smallest representable time unit on the system (line 35); on a system with microsecond resolution, this would be 106, and on a system with nanosecond resolution, this would be 109.
Time periods
Just as with dates, we can represent fixed time periods using boost::posix_time::time_period
. Here is a short example that shows how you can create time periods and compare different time periods:
Listing 8.6: Using time periods
1 #include <boost/date_time.hpp> 2 #include <iostream> 3 #include <cassert> 4 namespace greg = boost::gregorian; 5 namespace pt = boost::posix_time; 6 7 int main() 8 { 9 // Get current time 10 pt::ptime now1 = pt::second_clock::local_time(); 11 pt::time_period starts_now(now1, pt::hours(2)); 12 13 assert(starts_now.length() == pt::hours(2)); 14 15 auto later1 = now1 + pt::hours(1); 16 pt::time_period starts_in_1(later1, pt::hours(3)); 17 18 assert(starts_in_1.length() == pt::hours(3)); 19 20 auto later2 = now1 + pt::hours(3); 21 pt::time_period starts_in_3(later2, pt::hours(1)); 22 23 assert(starts_in_3.length() == pt::hours(1)); 24 26 std::cout << "starts_in_1 starts at " << starts_in_1.begin() 27 << " and ends at " << starts_in_1.last() << '\n'; 28 29 // comparing time periods 30 // non-overlapping 31 assert(starts_now < starts_in_3); 32 assert(!starts_now.intersects(starts_in_3)); 33 34 // overlapping 35 assert(starts_now.intersects(starts_in_1)); 36 37 assert(starts_in_1.contains(starts_in_3)); 38 }
We create a time period called starts_now
that starts at the current instant and extends for 2 hours into the future. For this, we use the two-argument constructor of time_period
, passing it the current timestamp and a duration of 2 hours (line 11). Using the length
member function of time_period
, we verify that the length of the period is indeed 2 hours (line 13).
We create two more time periods: starts_in_1
that starts 1 hour later and extends for a duration of 3 hours (line 16), and starts_in_3
that starts 3 hours later and extends for 1 hour (line 20). The member functions begin
and last
of time_period
return the first and last time points in the period (lines 26-27).
We express the relationships between the three time periods, starts_now
, starts_in_1
, and starts_in_3
, using relational operators and two member functions called intersects
and contains
. Clearly, the first hour of starts_in_1
overlaps with the last hour of starts_now
, so we assert that starts_now
and starts_in_1
intersect with each other (line 35). The last hour of starts_in_1
coincides with the entire period starts_in_3
, so we assert that starts_in_1
contains starts_in_3
(line 37). But starts_now
and starts_in_3
do not overlap; therefore, we assert that starts_now
and starts_in_3
do not intersect (line 32).
The relational operator<
is defined such that for two time periods tp1
and tp2
, the condition tp1 < tp2
holds if and only if tp1.last() < tp2.begin()
. Likewise, operator>
is defined such that the condition tp1 > tp2
holds if and only if tp1.begin() > tp2.last()
. These definitions imply that tp1
and tp2
are disjoint. Thus, for the disjoint time_period
s starts_now
and starts_in_3
, the relation starts_now < starts_in_3
holds (line 31). These relations do not make sense for overlapping time periods.
Time iterator
We can iterate over a time period using boost::posix_time::time_iterator
, not unlike how we used boost::gregorian::date_iterator
. The following example shows this:
Listing 8.7: Iterating over a time period
1 #include <boost/date_time.hpp> 2 #include <iostream> 3 4 namespace greg = boost::gregorian; 5 namespace pt = boost::posix_time; 6 7 int main() 8 { 9 pt::ptime now = pt::second_clock::local_time(); 10 pt::ptime start_of_day(greg::day_clock::local_day()); 11 12 for (pt::time_iterator iter(start_of_day, 13 pt::hours(1)); iter < now; ++iter) 14 { 15 std::cout << *iter << '\n'; 16 } 17 }
The preceding example prints the timestamp for each completed hour in the current day. We instantiate a time_iterator
(line 12), passing it the time point from where to begin the iteration (start_of_day
) and the duration added for each increment of the iterator (1 hour). We iterate till the current time, incrementing printing the timestamp obtained by dereferencing the iterator (line 15) and incrementing the iterator (line 13). Notice that in the expression iter < now
, we compare the iterator with a time point to decide when to stop iteration—a peculiar property of posix_time::time_iterator
, which is not shared with other iterators.