Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Learning Boost C++

You're reading from   Learning Boost C++ Solve practical programming problems using powerful, portable, and expressive libraries from Boost

Arrow left icon
Product type Paperback
Published in Jul 2015
Publisher
ISBN-13 9781783551217
Length 558 pages
Edition 1st Edition
Languages
Tools
Arrow right icon
Author (1):
Arrow left icon
Arindam Mukherjee Arindam Mukherjee
Author Profile Icon Arindam Mukherjee
Arindam Mukherjee
Arrow right icon
View More author details
Toc

Table of Contents (14) Chapters Close

Preface 1. Introducing Boost FREE CHAPTER 2. The First Brush with Boost's Utilities 3. Memory Management and Exception Safety 4. Working with Strings 5. Effective Data Structures beyond STL 6. Bimap and Multi-index Containers 7. Higher Order and Compile-time Programming 8. Date and Time Libraries 9. Files, Directories, and IOStreams 10. Concurrency with Boost 11. Network Programming Using Boost Asio A. C++11 Language Features Emulation Index

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 type boost::gregorian::date_duration. It refers to the same type as boost::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 type boost::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 type boost::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 type boost::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 type boost::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_periods 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.

lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Banner background image