Tutorial Part 1- A Portable Date Library for C++

Introduction

How do we work with dates and times in C++? One way is to use the C date and time library. This library has functions for manipulating times, such as time, clock and difftime; functions for converting formats of times and dates, e.g., gmtime, localtime, mktime; and various types, such as tm for calendar time, time_t for the time since the epoch, and clock_t, which denotes a process running time. In addition, since C++11 we’ve had a nice library for dealing with points in and durations of time, namely, chrono.

These libraries do have their problems though. The C library (not surprisingly) looks like just that – a C library, and not like C++ code. For example, it has no classes, there are no operators for performing such useful functions as determining the amount of time between two dates or whether one date comes before or after another date, and the internals of the library are exposed and often clumsy to work with. The chrono library overcomes these deficiencies because it was designed from the ground up to be in C++. Unfortunately, chrono has no facilities for working with calendar dates.

To let C++ work easily with dates, C++ guru Howard Hinnant has written two libraries. The first is the header-only library date.h, which is built upon chrono. It allows us to work with typical calendar time units, e.g., day, month, year. It also lets us convert between these units and the time units in chrono. The second library, in tz.h and tz.cpp, is built upon date.h and lets us work with time zones. Finally, in order to test his date library and demonstrate its power, Howard used it to write libraries for a Julian calendar, an Islamic calendar, and an ISO week date calendar.

There is a proposal to make date.h and tz.h part of the C++ standard, so we may eventually see a lot more of these libraries. In the mean time, they are ready to use, so in this post and some following ones, we’ll figure out how to work with them.

Setting up

To begin, we’ll get the date library, make a project to build a program to run code snippets, and learn how to construct dates with the library. So let’s start by getting the code.

Setting up the date library

To get the code go to the library’s GitHub site and click on the “Clone or Download” button. For the date tutorial, the only file we need is date.h . We can put it in whatever folder we want to.

The library’s documentation is here. It has a table of contents at the top. This blog post will roughly follow the overview section. There’s detailed documentation for the types, functions and other C++ elements in the reference section.

Setting up the project

In this post we’ll just use a simple console program to compile and run snippets that demonstrate parts of the library. To use the date library, we’ll need to have a compiler that works with at least C++11. If you’re not using Embarcadero’s C++ Builder, go ahead and set up your project now. You’ll need to add the path to the folder where you stored date.h to the project’s include paths.

I’ll be using Embarcadero C++ Builder 10.2 Tokyo with the bcc32c compiler. This is a compiler that creates 32-bit code and is based on CLANG 3.3. If you’re also using C++ Builder, here’s how to set up the project, with links to more detailed instructions:

  1. In Builder, create a console application, setting Project Framework to None
  2. Add to the include paths for “32-bit Windows platform” the path to the folder where date.h is
  3. Make the project use the 32-bit CLANG compiler
  4. Include the conditional define _HAS_CONSTEXPR. We need this for Builder 10.2 Tokyo. We don’t need it for 10.1 Berlin, but it doesn’t hurt to have it either

Now, whether you’re using Builder or not, change your source file to look like this:

#include <iostream>
#include "date.h"

using namespace std::chrono;
using namespace date;
using namespace std;

int main()
{
   return 0;
}

We’re ready to try out some code snippets now. The snippets will always go above line 10, so for brevity, I won’t show the above code anymore – just the snippets.

Creating dates

Before we start the code, let’s talk a little about how people express dates. A date has three components – the day, the month and the year. According to this article in Wikipedia, people all over the world use only three orders of the components – year, month, day; day, month, year; or month, day, year. They also only use four symbols to separate the components – slash (/), dot (.), dash (-) or space ( ).

Creating constant-expression dates

The date library makes ingenious use of C++’s division operator (/) to allow us to construct a date using a notation that’s very similar to what people write by hand when they use a slash separator. Try compiling this snippet:

constexpr auto x1 = 2017_y/apr/12;

CAUTION – If you’re using a Microsoft compiler that is not CLANG-based and came before Visual Studio 2017, you’ll need to replace constexpr with const here and in the remaining snippets. If you have a different compiler but get an error on the above line, try that substitution too.

The first thing to notice in the snippet is the strange-looking 2017_y, whose suffix implies that 2017 refers to a year. 2017_y is an example of a C++11 user-defined literal. Just as we can use built-in suffixes to tell the compiler what the type of a sequence of numerals represents, e.g., 3.1415f is a float, 1ULL is an unsigned long long, we can define our own literals. When the compiler sees a user-defined literal it passes the base part to a user-defined literal operator, which returns a value of a certain type. In the case of 2017_y, the compiler passes 2017 to a literal operator defined in date.h, which returns 2017 stored as a year type. In addition to year, date.h defines many other types, including day, month, weekday and year_month_day.

The next thing to notice is apr, which is a three-letter abbreviation for the month of April. The date header provides the following:

  • jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec as month types
  • sun, mon, tue, wed, thu, fri, sat as weekday types
  • the literal suffix _d, which acts like _y but returns a day type

The date header provides an operator/ that accepts a year on the left, a month on the right and returns a year_month. Another operator/ accepts that as its left argument, and the int denoting the day as its right argument and returns a year_month_day. Looking back at the snippet we see that the compiler stores the result in the variable x1. The variable has the type year_month_day (because of auto) and the compiler evaluates it at compile time (because of constexpr).

date.h contains many other forms of the division operator, and these lead to an ingenious technique for constructing dates. Assuming that the date is in one of the three forms that people use, i.e.,

  • year, month, day
  • day, month, year
  • month, day, year

specifying the type of only the first element of the date lets the compiler deduce which of the three forms of the date we’re using. Moreover, it prohibits the use of any illegal combination of types of the second and third elements by producing a compiler error. For example, if the first type were year, the second and third types could be month and day respectively, but day and month would not compile. We can still fool the compiler by creating the month and day as ints and then making the second element be the day and the third the month, but at least the built-in protections catch many other mistakes.

We can make a constant-expression or run-time date of the above three forms by specifying the type of the first element, and then any combination of type or int constant for the second and third elements. For example, if the first element is a year, there are four possible combinations of the last two elements:

constexpr auto x1 = 2017_y/apr/12_d;
constexpr auto x2 = 2017_y/apr/12;
constexpr auto x3 = 2017_y/4/12_d;
constexpr auto x4 = 2017_y/4/12;

Here are some examples where the first element is not a year:

constexpr auto x1 = apr/12/2017;
constexpr auto x2 = 12_d/apr/2017;
constexpr auto x3 = 12_d/4/2017;

Since only one of the three legal forms has a day in the middle, we can also get the same compiler protections by putting a day type in the middle.

constexpr auto x1 = 4/12_d/2017;      // good
constexpr auto x2 = apr/12_d/2017;    // good
constexpr auto x3 = 2017/12_d/apr;    // bad - year/day/month not legal form

All of the methods that we’ve looked at so far for making a constexpr date create a year_month_day type. This is one of the major types in the library. One nice feature of the type is that we can insert it into an output stream to get a legible date in the form “yyyy-mm-dd”. The snippet below illustrates this capability.

constexpr auto x = 4/12_d/2017;
cout << x << '\n';

displays

2017-04-12

The date library provides a couple of additions that make specifying dates more flexible. One of these is the type last, which represents the last day of the month. We can use that type wherever we can use the day type. Dates created with last have the type year_month_day_last. This type functions in almost the same way that year_month_day does. We can also construct a year_month_day object from a year_month_day_last object.

CAUTION – Unfortunately, CLANG 3.3 and some other early compilers had problems with using initializer lists, i.e., values placed within braces (curly brackets) for the purpose of constructing an object. In the snippet below, Builder won’t compile the lines that contain a month or day initialized with braces. If this happens, try replacing  constexpr with const.  If that doesn’t work, also replace the braces with parentheses. In either case, the constructed variable will be constant at run time but can’t be constructed at compile time.

constexpr auto ymdl_2016 = feb/last/2016;
constexpr auto ymdl_2017 = 2017_y/feb/last;

// Next two lines should compile but don't with some early compilers
constexpr auto ymd_2016 = year_month_day{ ymdl_2016 };
constexpr auto ymd_2017 = year_month_day{ ymdl_2017 };

// Next two lines show work-around for early compilers
//const auto ymd_2016 = year_month_day( ymdl_2016 );
//const auto ymd_2017 = year_month_day( ymdl_2017 );

cout << ymdl_2016 << '\n' << ymdl_2017 << '\n'
<< ymd_2016 << '\n' << ymd_2017 << '\n';

displays

2016/Feb/last
2017/Feb/last
2016-02-29
2017-02-28

Another goodie that helps in making constant-expression dates is the indexed weekday, which is a way to denote the nth weekday of the month for that year. We make an indexed weekday by appending the index in brackets to a month literal, e.g., sun[4], thu[1]. The index has a small range – one through five. A date made with an indexed weekday has the type year_month_weekday and we can use it wherever we use a day. We can also explicitly construct a year_month_day from a year_month_weekday.

constexpr auto x1 = 2017_y/apr/wed[2];

// Next line should compile but doesn't with some early compilers
constexpr auto x2 = year_month_day{ x1 };

// Next line shows work-around for early compilers
// const auto x2 = year_month_day{ x1 };

cout << x1 << '\n' << x2 << '\n';

displays

2017/Apr/Wed[2]
2017-04-12

The last helpful feature for making dates that we’ll discuss is actually a combination of the previous two. We can use last as the index of an indexed weekday to obtain the last occurrence of that weekday in the specified year and month. The resulting type of a date made with last as an index is year_month_weekday_last.

constexpr auto x1 = 2017_y/apr/sun[last];

// Next line should compile but doesn't with some early compilers
constexpr auto x2 = year_month_day{x1};

// Next line shows work-around for early compilers
//const auto x2 = year_month_day{x1};

cout << x1 << '\n' << x2 << '\n';

displays

2017/Apr/Sun[last]
2017-04-30

A final note on constant-expression dates – we can use a more traditional style of constructor if we don’t like creating these dates with the division operator. For example,

constexpr auto x1 = 2017_y/apr/sun[last];
constexpr auto x2 = year_month_weekday_last { year{2017}, month{4},
     weekday_last{ weekday{0u} } };
static_assert( x1==x2, "x1 and x2 have the same value" );
static_assert( is_same< decltype(x1), decltype(x2) >::value,
     "x1 and x2 have the same type" );
cout << x1 << '\n' << x2 << '\n';

displays

2017/Apr/Sun[last]
2017/Apr/Sun[last]

Since compiling the code didn’t trigger the static_asserts, the two variables have the same type and value.

Creating dates at run time

We can use the code for creating constant-expression dates to create run-time dates. (Of course, we have to omit constexpr for run-time dates.) In addition, we can create variables of type year, month or day from integers at run-time. Here are some examples:

int y = 2017;
int m = 4;
int d = 17;
auto x1 = year{y}/m/d;

// Next three lines should compile but don't with some early compilers
auto x2 = month{m}/d/y;
auto x3 = day{d}/m/y;
auto x4 = m/day{d}/y;

// Next three lines show work-around for early compilers
//auto x2 = month(m)/d/y;
//auto x3 = day(d)/m/y;
//auto x4 = m/day(d)/y;

cout << x1 << '\n' << x2 << '\n' << x3 << '\n'
     << x4 << '\n' << x5 << '\n';

displays

2017-04-17
2017-04-17
2017-04-17
2017-04-17

Well, this post ended up being a lot longer than I thought it would be. In future posts we won’t have to get the date code and set up the project. This will give us more time to actually use the dates we create.

Acknowledgements

Thanks to Howard Hinnant for reviewing a preliminary version of this post.

More of the tutorial

Part 2

Save

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s