A better date and time C++ library

C++11 added a date and time utility library called chrono, available in namespace std::chrono and header <chrono>. The problem with it is that the library is a general purpose one and therefore lacks many useful features, such as working with dates, weeks, calendars, timezones and other related features. Fortunately, a rich date and time library based on chrono has been created by Howard Hinnant and is available on github. The library is called date and is actually a collection of several small libraries:

  • date: the main library, available in header date.h, defines new date and time classes and operations with them. All the other libraries are based on this one.
  • timezones: a library for timezones, available in files tz.h/tz.cpp, based on the IANA timezone database
  • chrono_io: a library for streaming durations, available in header chrono_io.h
  • iso_week: a library that implements the ISO week calendar, available in header iso_week.h
  • julian and islamic: libraries that implement the Julian and Islamic calendars, available in headers julian.h and islamic.h

You can find all the necessary documentation on github. Here are several links:

In this article we will look at some examples for working with dates and ISO weeks. This library introduces many new types to handle various date and time representations. Among these we will look at:

  • sys_days: A count of days since std::system_clock‘s epoch. This is a time_point with a resolution of a day, and is implicitly convertible to std::system_clock::time_point, that has a much smaller resolution (millisecond or nanosecond), but not the other way around. To go the other way you must use floor().
  • year_month_day: A type that holds a day with fields for year, month (1 to 12) and day (1 to 31).
  • year_month_weekday: A type that holds a day with fields for year, month (1 to 12), a day of the week (0 to 6), and an index in the range [1, 5] that indicates the number of the week in the month.
  • year_weeknum_weekday: A type that hold a year, a weeknum (1 to 53) and a weekday (0 to 6). This can convert implicitly to and from a sys_days.

For using the library we need the following:

  • include header date.h and namespaces date and date::literals
  • for iso weeks we also need header iso_week.h and namespaces iso_week and iso_week::literals
  • NOTICE: The namespaces date::literals and iso_week::literals define types and literal operators with the same name and therefore can lead to name collisions; therefore you should only include them in the scope where you need them.

We will use the following lambda expression to print various dates to the console:

auto lprintdate = [](auto const & d) {std::cout << d << std::endl; };

NOTICE: All the ‘today’ and related dates below are based on 2016-10-31.

Let us look at some examples:

  • create sys_days objects (including literals):
    sys_days d1 = 2016_y / oct / 29;
    sys_days d2 = 29_d / oct / 2016;
    sys_days d3 = oct / 29 / 2016;
    auto today = floor<days>(system_clock::now());
    
    lprintdate(d1);      // 2016-10-29
    lprintdate(d2);      // 2016-10-29
    lprintdate(d3);      // 2016-10-29
    lprintdate(today);   // 2016-10-31
  • create year_month_day objects (including literals):
    year_month_day d1 = 2016_y / oct / 29;
    year_month_day d2 = 29_d / oct / 2016;
    year_month_day d3 = oct / 29 / 2016;
    year_month_day today = floor<days>(system_clock::now());
    
    lprintdate(d1);      // 2016-10-29
    lprintdate(d2);      // 2016-10-29
    lprintdate(d3);      // 2016-10-29
    lprintdate(today);   // 2016-10-31
  • creating year_month_weekday literals and converting to year_month_day
    auto wd1 = 2016_y / oct / mon[1];
    auto wd2 = mon[1] / oct / 2016;
    auto wd3 = oct / mon[1] / 2016;
    
    lprintdate(wd1);     // 2016/Oct/Mon[1]
    lprintdate(wd2);     // 2016/Oct/Mon[1]
    lprintdate(wd3);     // 2016/Oct/Mon[1]
    
    auto d1 = year_month_day{ wd1 };
    auto d2 = year_month_day{ wd2 };
    auto d3 = year_month_day{ wd2 };
    
    lprintdate(d1);      // 2016-10-03
    lprintdate(d2);      // 2016-10-03
    lprintdate(d3);      // 2016-10-03
  • create year_month_day values for today, yesterday and tomorrow
    auto today = floor<days>(system_clock::now());
    auto tomorrow = today + days{ 1 };
    auto yesterday = today - days{ 1 };
    
    lprintdate(yesterday);  // 2016-10-30
    lprintdate(today);      // 2016-10-31
    lprintdate(tomorrow);   // 2016-11-01
  • create year_month_day values for first and last day of the month
    auto today = year_month_day{ floor<days>(system_clock::now()) };
    auto first_day_this_month = year_month_day{ today.year(), today.month(), day{ 1 } };
    lprintdate(first_day_this_month);// 2016-10-01
    
    auto d1 = year_month_day_last(today.year(), month_day_last{ today.month() });
    auto last_day_this_month = year_month_day{ d1 };
    lprintdate(last_day_this_month); // 2016-10-31
    
    auto d2 = year_month_day_last(year{ 2016 }, month_day_last{ month{ 2 } });
    auto last_day_feb = year_month_day{ d2 };
    lprintdate(last_day_feb);        // 2016-02-29

    Update: The following, as indicated by Howard Hinnant in the comments, can also be used:

    year_month_day first_day_this_month = today.year()/today.month()/1;
    year_month_day last_day_this_month = today.year()/today.month()/last;
  • create iso_week literals
    auto isod1 = 2016_y / 42 / mon;
    auto isod2 = 42_w / mon / 2016_y;
    auto isod3 = mon / 42_w / 2016_y;
    
    lprintdate(isod1);  // 2016-W44-Mon
    lprintdate(isod2);  // 2016-W44-Mon
    lprintdate(isod3);  // 2016-W44-Mon
  • get the iso week number for today
    auto today = floor<days>(system_clock::now());
    auto today_iso = year_weeknum_weekday{ today };
    
    lprintdate(today_iso);  // 2016-W44-Mon
    std::cout << "week " << (unsigned)today_iso.weeknum() << std::endl; //44

We will look at more utilities and examples in another post.

8 Replies to “A better date and time C++ library”

  1. Awesome post!

    A few tiny nitpicks that should be easy to correct:

    * Re: “not compatible with the std::system_clock::time_point”

    `sys_days` is implicitly convertible to `system_clock::time_point` but not vice-versa. To go the other way use `floor` as shown in many places your article.

    * In the first code snippet, `d1`, `d2` and `d3` have type `year_month_day`. But you can manually make those `sys_days` instead of `auto`, and then it does exactly what you say.

    * Here are easier and more readable ways to get the first and last day of the current month:

    year_month_day first_day_this_month = today.year()/today.month()/1;
    year_month_day last_day_this_month = today.year()/today.month()/last;

    You could use `auto` for the first above. If you use `auto` for the second, the type is `year_month_day_last`, and that’s still ok. It will implicitly convert to `year_month_day`, but has the semantics of _always_ representing the last day of the month. For example you can add a month to it and get the last day of the next month.

  2. Nice article! How can I use this library to compute the day of the year? How can I compute the date of a given day in the year with respect to leap years? What happens when you add date::day{2} and date::day{30}? How to convert between day durations e.g. date::days and date::day? How to calculate with date::day or date::days?

  3. I am trying to find help/documentation on how to build on Windows using Visual Studio 2017 (aka vc15). I’ve used cmake and it generated the VS files but only for 32 bit. When I use the configuration mgr to generate 64 bit and then build I get the error LNK1112: module machine type x64 conflicts with target machine type 86.Ultimately I need to have this library built in all four build modes (debug/release, 32/64).

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.