Document number: Dxxxx=xx-xxxx

Howard Hinnant
2011-08-07

chrono::date

This work has been superseded. It is left here so that people can read the history if they really want to. But the latest word on this subject is found here:

date

Introduction

The subject of this paper is a date class which has been strongly influenced by boost::date, but is not proposing exactly what is currently boost::date. What is proposed herein is a relatively small library. The implementation is currently composed of 3 different performance design alternatives, all conforming to a single specification, and the total number of semicolons in the union of all three designs is only 523. Suffice it to say that we are talking approximately 400 lines of C++ code to implement any one of the 3 implementation design alternativs prototyped in the example code, not 5000 lines of C++ code (the approximate size of boost date time). The API proposed herein is correspondingly small, and yet powerful enough to enable the client to express everything needed in this area.

The three example implementations can be found in <date> and date.cpp.

The boost::date API is considerably larger. Additionally the boost::date API allows for a few situations which can be visually ambiguous. For example, here is an example using boost with the intent of forming January 2, 2011:

#include "boost/date_time/gregorian/gregorian.hpp"
#include <iostream>

int main()
{
    using namespace boost::gregorian;
    // I'm from the US and am used to month/day/year
    std::cout << date(1, 2, 2011) << '\n';
}

The above results in a run time error because the boost::date constructor expects the order of arguments to be year, month, day. This proposal allows the entry of day, month and year to vary. Additionally the first two entries must be a type that unambiguously gives the units (day, month or year). For example:

std::cout << month(1)/day(2)/2011 << '\n';  // 2011-01-02

If you prefer the year/month/day order, that is just as easily accomplished:

std::cout << year(2011)/month(1)/2 << '\n';  // 2011-01-02

This library is placed in namespace std::chrono. This is just another time-keeping library, but it counts sunrises instead of seconds. And it interoperates with the existing chrono library.

date Construction

Significant effort has been put into designing a library that makes it easy to correctly construct a date, unlikely to construct an unexpected date, and impossible to construct an invalid date.

In all a chrono::date can be entered with three variations of the order of years, months and days. The first two units must be specified, and disambiguates which order you are using. Use of explicit units for the third unit is optional. These three orders were chosen out of the six possible orders because these are the only orders that people actually use worldwide.

int m, d, y;
// give values to m, d and y ...
date d1 = year(y)  / month(m) / day(d);   // ymd ok
date d2 = month(m) / day(d)   / year(y);  // mdy ok
date d3 = day(d)   / month(m) / year(y);  // dmy ok

The other three orderings of year, month, and day are rejected at compile time.

int m, d, y;
// give values to m, d and y ...
date d1 = year(y)  / day(d)  / month(m);  // error: use of overloaded operator '/' is ambiguous
date d2 = month(m) / year(y) / day(d);  // error: invalid operands to operator '/'
date d3 = day(d)   / year(y) / month(m);  // error: invalid operands to operator '/'

The rationale for this is that there exists no country on the planet which uses any other ordering. This is in contradiction to [locale.time.get] which specifies the above three orderings plus a ydm ordering. There is no inconsistency in not offering a year/day/month ordering. No current code will be invalidated. This is simply a lack of support for the ydm ordering.

Furthermore, there exists const objects of type month named jan, feb, ... dec. These can be used exactly as month(m) is used:

date d1 = jan/day(2)/2011;   // jan/2/2011
date d2 = year(2011)/jan/2;  // jan/2/2011
date d3 = day(2)/jan/2011;   // jan/2/2011

This makes creating date literals very easy, not prone to error due to ordering issues, and unambiguous to read. Also in this proposal it is not possible to create an invalid date. As dates are constructed or modified by date arithmetic, range checking is done, and an exception (bad_date) is thrown if any field of the date goes out of range.

Additionally there are const objects with the following names that can be used to specify the day of the month:

_1st
_2nd
_3rd
_4th
_5th
last

Now you can write (for example):

date d1 =  jan/_2nd/2011;          // jan/2/2011
date d2 =  year(2011)/jan/_2nd;    // jan/2/2011
date d3 =  _2nd/jan/2011;          // jan/2/2011

Note the constant last. This provides a very easy way to specify the last day of the month:

date d1 = last/jan/2011;  // jan/31/2011

Sometimes you don't know exactly which day of the month you want, but instead know what weekday of the month you want. For example Mother's Day is celebrated on the second Sunday in May in the US:

date MothersDay = sun[2]/may/2011;     // may/8/2011
date MothersDay = sun[_2nd]/may/2011;  // may/8/2011

If you ask for a non-existent date (e.g. the fifth Friday of May in 2011) a bad_date exception will be thrown. If what you really want is the fifth Friday in May only if it exists, else the fourth Friday, you can use last.

date d1 = fri[last]/may/2011;  // may/27/2011

If you want to find out how many Fridays there are in May 2011, it is easy to code with:

int num_fri_in_may = (fri[last]/may/2011).day() > 28 ? 5 : 4;  // 4

If you don't know which day of the week you're looking for until run time, you can use weekday in the exact same way you use the const objects sun through sat:

int wd = ...
date d1 = weekday(wd)[_1st]/may/2011;

Creating dates is safe, easy, intuitive, and efficient.

date Arithmetic

Date arithmetic is supported for the units of days, months and years. In the chrono calendar, the length of months and years is not a constant number of days. This complicates the meaning of arithmetic with months and years. This library takes a pragmatic approach to this problem which gives clients choices on how they want to perform the arithmetic.

Year-oriented Arithmetic

For every day of the year but one, adding a year to the date has a straight forward meaning: it modifies the year of the date, but not the day of the month or month of the year. For example oct/day(15)/2011 + years(1) == oct/day(15)/2012. But what about Feb. 29?

The boost library addresses this issue by "snapping to the end of the month." That is Feb. 29, 2012 + 1 year is Feb. 28, 2013. And Feb. 28, 2011 + 1 year is Feb. 29, 2012. But consider this example: John's birthday is Feb. 28. And he wants to print out his birthday for the decade. Using boost this looks like:

// Print Feb. 28 for each year in the decade
for (date d(2010, Feb, 28), e(2020, Feb, 28); d <= e; d += years(1))
    std::cout << d << '\n';

2010-Feb-28
2011-Feb-28
2012-Feb-29
2013-Feb-28
2014-Feb-28
2015-Feb-28
2016-Feb-29
2017-Feb-28
2018-Feb-28
2019-Feb-28

Using the boost design, every leap year the output prints out Feb. 29 instead of Feb. 28. But John wasn't born on Feb. 29! That isn't the behavior he wants. He finds this behavior surprising.

This library prints out Feb. 28 for each year using the syntax below.

// Print Feb. 28 for each year in the decade
for (date d = feb/day(28)/2010, e = feb/day(28)/2020; d != e; d += years(1))
    std::cout << d << '\n';

2010-02-28
2011-02-28
2012-02-28
2013-02-28
2014-02-28
2015-02-28
2016-02-28
2017-02-28
2018-02-28
2019-02-28

And if you add 1 year to feb/day(29)/2012, a bad_date exception is thrown because there is no feb/day(29)/2013. You get exactly the same result as if you had attempted to construct feb/day(29)/2013 directly.

But now Sue comes along, and she happens to have been born on Feb. 29. And she doesn't want to have to wait 4 years to celebrate her birthday. She decides that she wants to celebrate her birthday on the last day of Feb. every year. She can print out her birthdays just as easily as John did:

// Print the last day in  Feb. for each year in the decade
for (date d = feb/last/2010, e = feb/last/2020; d != e; d += years(1))
    std::cout << d << '\n';

2010-02-28
2011-02-28
2012-02-29
2013-02-28
2014-02-28
2015-02-28
2016-02-29
2017-02-28
2018-02-28
2019-02-28

When year-oriented arithmetic is applied to a date that has been constructed with last, the library knows that although feb/last/2011 == feb/day(28)/2011, feb/last/2011 + years(1) == feb/day(29)/2012. And the result of this computation behaves as if it had been constructed with feb/last/2012, not feb/day(29)/2012. Thus throughout the for-loop above, the variable d always represents the last day of Feb., no matter what the year is.

So this library enables both desired behaviors (do not "snap-to" and "snap-to"), and also delivers results that are not surprising to the casual reader of the code. And the arithmetic is reversible. If you add a year to a date (and this results in a valid date), and then subtract a year from that result, then you always get back your original date. This is not true of the boost date library. For example with boost, if you add a year to Feb. 28, 2012, you get Feb. 28, 2013. If you then subtract a year, you get Feb. 29, 2012, not the original date Feb. 28, 2012.

To complete the birthday example, Sam, like Sue, was born on Feb. 29. But unlike Sue, he wants to always celebrate his birthday on the day after Feb. 28. This is also easy to accomplish:

// Print the day after Feb. 28 for each year in the decade
for (date d = feb/day(28)/2010, e = feb/day(28)/2020; d != e; d += years(1))
    std::cout << d + days(1) << '\n';

2010-03-01
2011-03-01
2012-02-29
2013-03-01
2014-03-01
2015-03-01
2016-02-29
2017-03-01
2018-03-01
2019-03-01

How expensive is all this?!

The implementation cost of this behavior is very inexpensive. It need not impact the sizeof(date) at all, no matter what strategy the vendor is using to store the date. The run time cost is minimal, involving simply checking a few bits of the representation to choose the exact arithmetic algorithm. These assertions are backed by an example implementation for each of the three obvious storage strategies:

  1. Store year, month, day, and a count of days since the epoch. (Storage space is 64 bits)
  2. Store a count of days since the epoch. (Storage space is 32 bits)
  3. Store year, month, and day. (Storage space is 32 bits)

In each example implementation above, 6 bits of storage are used to store information such as: this date represents the last day of February. These 6 bits do not participate in the date comparison operations. The remaining bits (58 or 26) are more than sufficient to store the indicated data.

In an attempt to quantify the cost of this library, I compared it with boost for the case that they both produce the same results: computing the last day of Feb. for years 2010 thru 2020.:

for (date d(2010, Feb, 28), e(2020, Feb, 28); d <= e; d += years(1))

vs.

for (date d = feb/last/2010, e = feb/last/2020; d != e; d += years(1))

Because I/O is rather expensive I set the body of the for loop to just:

;

I wrapped the for loop with calls to std::chrono::high_resolution_clock::now() and printed out the results in units of microseconds (represented as a double). I also printed out the sizeof date. I ran each case 3 times and am reporting here the average of those 3 runs. I also ran this test for each of the 3 std::chrono implementations described above. All tests were run with clang -O3. The variance of the 3 runs for each implementation is considerably less than the difference between the averages reported here.

boost:

5.08 microseconds
sizeof(date) = 4

chrono, implementation 1:

1.80 microseconds
sizeof(date) = 8

chrono, implementation 2:

2.64 microseconds
sizeof(date) = 4

chrono, implementation 3:

1.75 microseconds
sizeof(date) = 4

While this is clearly not a comprehensive performance test, it does at least establish that this proposal is in the same ballpark, performance wise, as the boost date library. The boost storage design is most analogous to what this paper calls "implementation 2". This storage strategy will excel at adding days to a date as this operation has no need to convert between the count of days since the epoch, and the year, month, day triple.

Again, this proposal is neutral on the storage strategy used (1, 2 or 3). Each has advantages and disadvantages.

Each of the above example implementations support a range of year(numeric_limits<short>::min())/jan/1 through year(numeric_limits<short>::max())/dec/31 inclusive (range governed by representing the year by a short). Thus this is a proleptic Gregorian calendar. The year preceding year 1 is year 0, and year 0 is a leap year. This is consistent with the definition of an expanded calendar given in ISO 8601:2004: Data elements and interchange formats — Information interchange — Representation of dates and times.

Month-oriented Arithmetic

One can add and subtract months to a date with the same ease and semantics as is done with years. For example you can add a month to jul/day(31)/2011 which results in aug/day(31)/2011. But if you add a month to aug/day(31)/2011 a bad_date is thrown since sep/day(31)/2011 does not exist. However you can always add a month to the _1st day of the month, or to any day of the month <= 28, or to the last day of the month. For example:

// Print last day of every month in 2011
for (date d = jan/last/2011, e = dec/last/2011; d <= e; d += months(1))
    std::cout << d << '\n';

2011-01-31
2011-02-28
2011-03-31
2011-04-30
2011-05-31
2011-06-30
2011-07-31
2011-08-31
2011-09-30
2011-10-31
2011-11-30
2011-12-31

// Print the 28th of every month in 2011
for (date d = jan/day(28)/2011, e = dec/day(28)/2011; d <= e; d += months(1))
    std::cout << d << '\n';

2011-01-28
2011-02-28
2011-03-28
2011-04-28
2011-05-28
2011-06-28
2011-07-28
2011-08-28
2011-09-28
2011-10-28
2011-11-28
2011-12-28

It is also easy to print out the 29th of every month. However one needs to explicitly decide what you want to do for months with less than 29 days. One obvious choice is to simply skip such months:

// Print the 29th of every month in 2011
for (date d = last/jan/2011, e = last/dec/2011; d <= e; d += months(1))
    if (d.day() >= 29)
        std::cout << d.year()/d.month()/29 << '\n';

2011-01-29
2011-03-29
2011-04-29
2011-05-29
2011-06-29
2011-07-29
2011-08-29
2011-09-29
2011-10-29
2011-11-29
2011-12-29

Year and month-oriented arithmetic also respects nth-day-of-the-week dates. For example if you want to print out the 2nd Tuesday of every odd month that is easily done with:

// Print the 2nd Tuesday of every odd month in 2011
for (date d = tue[2]/jan/2011, e = dec/day(31)/2011; d <= e; d += months(2))
    std::cout << d << '\n';

2011-01-11
2011-03-08
2011-05-10
2011-07-12
2011-09-13
2011-11-08

This final example should be emphasized. Imagine you've just met an extended family at a restaurant, one whom you have not met before. They're celebrating. There's children, parents and grandparents present. They enjoy your company so much they invite you back for the same celebration, same place, "next year." You check the current date and it is May 8, 2011.

On what date should you plan to attend? If they were celebrating a birthday, then you should come back on May 8, 2012. But if they were celebrating Mother's Day then you should come back on May 13, 2012 (the second Sunday of May in 2012) — five whole days later!

Adding a year (or a month) to a date is intrinsically context sensitive.

The expressions d + years(1) and d + months(1) are only unambiguous when you know the context within which d was constructed. This library stores that context as part of d's state.

Day-oriented Arithmetic

Day-oriented arithmetic is intrinsically less complicated than month and year-oriented arithmetic. The chrono calendar is nothing but a count of sunrises, and a distinct name for each sunrise. You can add any number of days to any date and the result is always a valid date (unless one exceeds the valid range for years). For example:

// Print out every monday between jan/1/2011 and mar/1/2011;
for (date d = mon[_1st]/jan/2011, e = mar/_1st/2011; d <= e; d += days(7))
    std::cout << d << '\n';

2011-01-03
2011-01-10
2011-01-17
2011-01-24
2011-01-31
2011-02-07
2011-02-14
2011-02-21
2011-02-28

In the above example, the first Monday of the year is found, and then to get each Monday after that, one simply adds 7 days. There is always another Monday coming up!

Additionally, if you subtract two dates, the result is a chrono::duration with the name days.

// How many days between may/1/2011 and jan/1/2011?
int x = (may/_1st/2011 - jan/_1st/2011).count();  // x == 120

Question: So what happens when you subtract a day from aug/last/2011 and then add a day back? The resultant day will be equal to aug/day(31)/2011. But will it still represent the last day of the month as far as month and year arithmetic is concerned?

Answer: No. dates tagged with "last" information become untagged with that information as soon as they have days added to or subtracted from them. So while aug/last/2011 + months(1) is equal to sep/day(30)/2011, aug/last/2011 - days(1) + days(1) + months(1) results in a bad_date exception because sep/day(31)/2011 does not exist. This is the same behavior you would get if you added months(1) to aug/day(31)/2011.

date Observers

Given a date d you can ask for its day(), month() and year(). These each return a day, month and year respectively. Note that these returned types are not the durations days, months and years. Rather they are the unit-specifiers used to specify a day, month and year which you used to construct the date. Each unit-specifier is implicitly convertible to an int. Example uses:

date dt = aug/day(16)/2011;
int d = dt.day();   // d == 16
int m = dt.month(); // m == 8
int y = dt.year();  // y == 2011

And:

date dt = aug/day(16)/2011;
// ...
// Create date with the same month and year but on the 5th
date dt2 = dt.year()/dt.month()/5;  // aug/5/2011

Note that in the latter example if year() returned a simple int instead of a year then the construction of dt2 would start with an int instead of a year and thus not be a well-formed date.

To get the weekday (day of the week) of a date use the weekday() member function. This returns a weekday type which is implicitly convertible to int. One can use this to print out an int which represents the day of the week:

date dt = aug/day(16)/2011;
// What day of the week is this?
int wd = dt.weekday();  // 2 (Tuesday)

Or one can find the first same weekday of the month of the following year:

date dt = aug/day(16)/2011;
// ...
// Get the date that is the first occurrence of the same day of the week
//   in the same month of the next year
date dt2 = dt.weekday()[_1st]/dt.month()/(dt.year() + 1);  // aug/7/2012, first Tuesday of Aug 2012

This syntax has power. There are nearly infinitely many applications for a date class which we can not imagine. This library creates a small, simple, efficient and consistent language of dates and date arithmetic which is widely applicable to all of the date applications which we have yet to imagine. And because the API for this library is small, it is easy to teach, and easy to learn.

Finding the next or previous weekday

Sometimes, given a date, one needs to find the previous Monday, or the following Sunday. For example the ISO week-based year starts on the Monday that falls on or before Jan 4 of each year. With this library you can code that date for year y as:

date ISO_week_start = mon <= jan/day(4)/y;

That is, the expression wd <= x returns the date y such that y is the first date equal to or prior to x such that y.weekday() == wd. There are four such operations summarized here. Let wd be a weekday expression and d be a date expression:

wd < d Returns the first date prior to d that has wd as its weekday.
wd <= d Returns the first date on or prior to d that has wd as its weekday.
wd > d Returns the first date after d that has wd as its weekday.
wd >= d Returns the first date on or after d that has wd as its weekday.

date I/O

dates are obviously streamable. The default formatting is consistent with ISO 8601: yyyy-mm-dd, as has been alluded to in previous examples. Additionally there is a datepunct facet and a date_fmt manipulator. These are basically C++ wrappers around C's strftime. And they also serve as wrappers around the non-standard-but-popular strptime for parsing dates from an istream.

To demonstrate the ease with which dates can be formatted, I'm taking a real-world example from my wife: She once set up a recurring meeting for the odd Fridays of every month. That is, they met on the first, third, and if it existed, the fifth Friday of every month. When I asked her why, she said: "Every week was too often, and every other week wasn't often enough." <shrug>

Here is how you print out the odd Fridays of every month in 2011, using date_fmt to format the date however you want:

std::cout << date_fmt("%a %b %e, %Y");
// Print the odd fridays of every month in 2011
for (date d = fri[_1st]/jan/2011, e = dec/last/2011; d <= e; d += months(1))
{
    std::cout << d << '\n';             // first Friday of the month
    std::cout << d + days(14) << '\n';  // third Friday of the month
    date last_fri = fri[last]/d.month()/d.year();
    if (last_fri.day() >= 29)
        std::cout << last_fri << '\n';  // fifth Friday of the month if it exists
}

Fri Jan  7, 2011
Fri Jan 21, 2011
Fri Feb  4, 2011
Fri Feb 18, 2011
Fri Mar  4, 2011
Fri Mar 18, 2011
Fri Apr  1, 2011
Fri Apr 15, 2011
Fri Apr 29, 2011
Fri May  6, 2011
Fri May 20, 2011
Fri Jun  3, 2011
Fri Jun 17, 2011
Fri Jul  1, 2011
Fri Jul 15, 2011
Fri Jul 29, 2011
Fri Aug  5, 2011
Fri Aug 19, 2011
Fri Sep  2, 2011
Fri Sep 16, 2011
Fri Sep 30, 2011
Fri Oct  7, 2011
Fri Oct 21, 2011
Fri Nov  4, 2011
Fri Nov 18, 2011
Fri Dec  2, 2011
Fri Dec 16, 2011
Fri Dec 30, 2011

Interoperability with other calendar systems

There are other calendar systems besides the chrono (Gregorian) calendar. For example just to name a few:

This paper proposes only the Gregorian calendar because this represents existing practice for C, C++, POSIX, and ISO 8601. However one has to wonder: shouldn't we design a framework which can support any of the world's calendars?

I claim that such a general framework is unnecessary, and we would likely get it wrong. The reason it is unnecessary is that clients can easily write their own calendars which convert to and from the chrono calendar using only the public API proposed herein. Their calendar may or may not use an API similar to the chrono API. Aside from the Julian calendar, the I/O facets time_get, time_put, and datepunct are not reusable by these other calendars. Indeed, there is very little opportunity for code reuse by making a "calendar framework".

To demonstrate, I coded a Julian calendar which converts to and from the chrono calendar (using no knowledge whatsoever of the implementation of chrono). This calendar is not proposed but shown here only for demonstration purposes.

And here is example use showing how the two calendars can be converted to one another:

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

int main()
{
    std::cout << std::chrono::date_fmt("%a %b %e, %Y");
    std::chrono::date cd = std::chrono::date::today();
    julian::date jd(cd);
    std::cout << "Today is:\n"
              << cd << " in the std::chrono calendar and\n"
              << jd << " in the Julian calendar\n";
    jd -= julian::years(1800);
    cd = std::chrono::date(jd);
    std::cout << "1800 years ago the two calendars were aligned:\n"
              << cd << " in the std::chrono calendar and\n"
              << jd << " in the Julian calendar\n";
}

Today is:
Fri May  6, 2011 in the std::chrono calendar and
Fri Apr 23, 2011 in the Julian calendar
1800 years ago the two calendars were aligned:
Tue Apr 23, 0211 in the std::chrono calendar and
Tue Apr 23, 0211 in the Julian calendar

I firmly believe that any other calendar can be converted to and from the chrono calendar using the techniques shown here for the Julian calendar, and that we need to do nothing to enable clients to do so. Furthermore actually providing these other calendars is far outside of our scope.

A non-trivial example

There's nothing like real-world use to test an interface. This is when you find out if using a given API is like flying with the wind, or wading through waist-deep water. In this spirit we would like to present two user-written functions drawn from real-world usage. These two functions are not proposed, though they certainly could be.

std::tuple<int, std::chrono::weekday, std::chrono::year>
    date_to_week(std::chrono::date d)
std::chrono::date
    week_to_date(int weeknum, std::chrono::weekday wd, std::chrono::year y);

These functions convert a std::chrono::date to and from the ISO 8601 week-based year format. The rules for this format are:

  1. The week starts with Monday, and ends with Sunday.
  2. The first day of the year is the Monday that occurs on or before Jan. 4.
  3. A date is specified by the week number [1 - 53], day of the week [Mon - Sun] and year number.
  4. The year number is the same as the Gregorian year number for the Thursday that follows the start of the week-based year.

ISO 8601 gives two examples:

  1. Sunday Jan. 1, 1995 is the Sunday of week 52 of the year 1994. (Week 1 of 1995 starts on Monday Jan. 2, 1995)
  2. Tuesday Dec. 31, 1996 is the Tuesday of week 1 of the year 1997. (The first day of the week-based year 1997 begins on Monday Dec. 30, which is the first Monday on or before Jan. 4, 1997, a Saturday)

These rules seem complex. And yet the code using std::chrono::date to convert to and from the week-based year is remarkably compact and self-explanatory. First we present date_to_week, which returns a triple: week number, day of the week, and week-based year number:

std::tuple<int, std::chrono::weekday, std::chrono::year>
date_to_week(std::chrono::date d)
{
    using namespace std::chrono;
    month_day jan4 = jan/_4th;
    date start = mon <= jan4/d.year();
    if (d < start)
        start = mon <= jan4/(d.year()-1);
    else
    {
        date next_start = mon <= jan4/(start.year()+1);
        if (d >= next_start)
            start = next_start;
    }
    return std::tuple<int, weekday, year>((d - start).count()/7 + 1,
                                          d.weekday(),
                                          (thu > start).year());
}

The first line of code creates a "shortcut" for Jan. 4. This isn't necessary. It is used here to demonstrate the use of the month_day object. We could just have easily written jan/_4th/d.year() instead of jan4/d.year() (for example). Use of jan4 is purely a stylistic issue and has negligible performance impact.

The start of the ISO year is stored in start and is found by identifying the Monday on or before Jan. 4. If d falls before the start of the year we just computed, then d must be in the previous ISO year, and so start is recomputed. Else we need to make sure that d does not lie beyond the current ISO year. We compute the start of the next ISO year to check for that, and if necessary, set start to the next ISO year.

Now we have the start of the ISO year which contains d. It is now a simple process to compute the week number and year number for d which are returned in a tuple. Care is taken for the fact that weeks are numbered starting from 1, not 0. And the year number is that of the Thursday following the start of the ISO year. The day of the week remains unchanged from d's.

The reverse conversion, week_to_date is even simpler:

std::chrono::date
week_to_date(int weeknum, std::chrono::weekday wd, std::chrono::year y)
{
    using namespace std::chrono;
    return (mon <= jan/_4th/y) + days((weeknum - 1)*7 + (wd == 0 ? 6 : wd - 1));
}
  1. Compute the start of the week-based year y.
  2. Add to the start date 7 days for each full week, taking into account that the week numbers start at 1, not 0.
  3. Add to that the number of days wd is past Monday. Note that Sunday is 6 days past Monday, not one day before it.

These functions can now be used as:

date d = /* ... */
int weeknum;
weekday wd(0);
year y(0);
std::tie(weeknum, wd, y) = date_to_week(d);

and

d = week_to_date(weeknum, wd, y);

Notes:

Proposed Wording

Text formatted like this is intended to be rationale for some of the proposed wording. It is not part of the proposed wording.

Date utilities

In general

This subclause describes the chrono calendar library which provides date generation and date arithmetic facilities.

Header <date> synopsis

There is an obvious lack of use of constexpr below. This is simply because I currently lack a compiler to test on which implements constexpr, and I don't propose what I can not test. Daniel Krügler has done extensive design and testing on this proposal to bring it into a form that uses constexpr. I fully support and embrace the use of constexpr in this proposal despite it being absent in this revision.
namespace std {
namespace chrono {

// error handling

class bad_date;

// Unit specifiers

class day;
class month;
class year;
class weekday;
class year_month;
class month_day;

// Specifier constants

extern const see below _1st;
extern decltype(_1st)  _2nd;
extern decltype(_1st)  _3rd;
extern decltype(_1st)  _4th;
extern decltype(_1st)  _5th;
extern decltype(_1st)  last;

extern const month jan;
extern const month feb;
extern const month mar;
extern const month apr;
extern const month may;
extern const month jun;
extern const month jul;
extern const month aug;
extern const month sep;
extern const month oct;
extern const month nov;
extern const month dec;

extern const weekday sun;
extern const weekday mon;
extern const weekday tue;
extern const weekday wed;
extern const weekday thu;
extern const weekday fri;
extern const weekday sat;

// Date generation functions

month_day operator/(day, month) noexcept;
month_day operator/(month, day) noexcept;

year_month operator/(year, month) noexcept;

date operator/(year_month, day);
date operator/(month_day, year);
date operator/(year_month, int);
date operator/(month_day, int);

// Durations

typedef duration<int_least32_t, ratio<86400>> days;

class months;

// months arithmetic

months operator+(months x, months y) noexcept;
months operator-(months x, months y) noexcept;
months operator*(months x, months::rep y) noexcept;
months operator*(months::rep x, months y) noexcept;
months operator/(months x, months::rep y) noexcept;
months::rep operator/(months x, months y) noexcept;
months operator%(months x, months::rep y) noexcept;
months operator%(months x, months y) noexcept;

// months comparisons

bool operator==(months x, months y) noexcept;
bool operator!=(months x, months y) noexcept;
bool operator< (months x, months y) noexcept;
bool operator> (months x, months y) noexcept;
bool operator<=(months x, months y) noexcept;
bool operator>=(months x, months y) noexcept;

class years;

// years arithmetic

years operator+(years x, years y) noexcept;
years operator-(years x, years y) noexcept;
years operator*(years x, years::rep y) noexcept;
years operator*(years::rep x, years y) noexcept;
years operator/(years x, years::rep y) noexcept;
years::rep operator/(years x, years y) noexcept;
years operator%(years x, years::rep y) noexcept;
years operator%(years x, years y) noexcept;

// years comparisons

bool operator==(years x, years y) noexcept;
bool operator!=(years x, years y) noexcept;
bool operator< (years x, years y) noexcept;
bool operator> (years x, years y) noexcept;
bool operator<=(years x, years y) noexcept;
bool operator>=(years x, years y) noexcept;

// date

class date;

// date comparisons

bool operator==(const date& x, const date& y) noexcept;
bool operator!=(const date& x, const date& y) noexcept;
bool operator< (const date& x, const date& y) noexcept;
bool operator> (const date& x, const date& y) noexcept;
bool operator<=(const date& x, const date& y) noexcept;
bool operator>=(const date& x, const date& y) noexcept;

// date day arithmetic
date operator+(date dt, days d);
date operator+(days d, date dt);
date operator-(date dt, days d);
days operator-(date x, date y) noexcept;

// date month arithmetic
date operator+(date dt, months m);
date operator+(months m, date dt);
date operator-(date dt, months m);

// date year arithmetic
date operator+(date dt, years y);
date operator+(years y, date dt);
date operator-(date dt, years y);

// find prior / next weekday
date operator< (weekday wd, date x);
date operator<=(weekday wd, date x);
date operator> (weekday wd, date x);
date operator>=(weekday wd, date x);

// date I/O

template <class charT> class datepunct;

template<class charT>
unspecified
date_fmt(basic_string<charT> fmt);

template<class charT>
unspecified
date_fmt(const charT* fmt);

template<class charT, class Traits>
basic_istream<charT,Traits>&
operator>>(basic_istream<charT,Traits>& is, date& d);

template<class charT, class Traits>
basic_ostream<charT, Traits>&
operator<<(basic_ostream<charT, Traits>& os, const date& d);

}  // namespace chrono
}  // namespace std

Class bad_date

namespace std {
namespace chrono {
  class bad_date
    : public runtime_error  
{
  public:
    explicit bad_date(const string& s);
    explicit bad_date(const char* s);
  };
}
}

The class bad_date is thrown when an exceptional condition is created within the chrono library.

bad_date(const string& s);

Effects: Constructs an object of class bad_date.

Postcondition: what() == s.

bad_date(const char* s);

Effects: Constructs an object of class bad_date.

Postcondition: strcmp(what(), s) == 0.

Unit specifiers

The classes defined in this section represent the different components of a date and when properly combined represent a date.

Each unit specifier is implicitly convertible to int. This conversion is implicit because an explicit conversion would require an excessively clumsy synax for some common use cases. For example:

date d1 = ...;
date d2 = d1.weekday()[_1st] / d1.month() / (d1.year() + 1);

vs

date d2 = d1.weekday()[_1st] / d1.month() / (static_cast<int>(d1.year()) + 1);

It would also not be appropriate to create arithmetic for the unit specifiers. This would blur the distinction between date unit specifiers and date durations.

The simple fact is that people specify days, months and years using integers just as often (if not more so) than using words. This library strives to make the syntax as natural as possible without sacrificing type safety. The implicit conversion from unit specifier to int (but not vice-versa) does not compromise this type safety.

Class day
namespace std {
namespace chrono {
  class day
  {
  public:
    explicit day(int d);
    day(decltype(_1st) d) noexcept;
    operator int() const noexcept;

    day() = delete;
    day(const day&) = default;
    day& operator=(const day&) = default;
    ~day() = default;
  };

  extern const see below _1st;
  extern decltype(_1st)  _2nd;
  extern decltype(_1st)  _3rd;
  extern decltype(_1st)  _4th;
  extern decltype(_1st)  _5th;
  extern decltype(_1st)  last;
}
}

The class day is used to specify the day of the month when constructing a date. It is capable of storing the day number of any month, a day of the week (Sunday thru Saturday), and a small integral value that indicates which weekday of a month the day represents (example: 2nd Sunday).

explicit day(int d);

Effects: Constructs an object of class day by storing d.

Postconditions: static_cast<int>(*this) == d

Throws: if d is outside of the range [1, 31], throws an exception of type bad_date.

day(decltype(_1st) d) noexcept;

Effects: Constructs an object of class day by storing d.

Postconditions: The value returned by casting *this to int depends on the value of d as follows:

operator int() const noexcept;

Returns: If *this has a value that resulted from constructing a day from an int, returns the value of that int. Otherwise the value returned is specified as above.

The type of _1st is an unspecified class type except that it is in the same namespace as day, it is CopyConstructible, CopyAssignable and Destructible. It is not DefaultConstructible.

Class month
namespace std {
namespace chrono {
  class month
  {
  public:
    explicit month(int m);
    operator int() const noexcept;

    month() = delete;
    month(const month&) = default;
    month& operator=(const month&) = default;
    ~month() = default;
  };

  extern const month jan;
  extern const month feb;
  extern const month mar;
  extern const month apr;
  extern const month may;
  extern const month jun;
  extern const month jul;
  extern const month aug;
  extern const month sep;
  extern const month oct;
  extern const month nov;
  extern const month dec;
}
}

The class month is used to specify the month of the year when constructing a date.

explicit month(int m);

Effects: Constructs an object of class month by storing m.

Postconditions: static_cast<int>(*this) == m

Throws: if m is outside of the range [1, 12], throws an exception of type bad_date.

operator int() const noexcept;

Returns: the value of the stored int.

These const month objects are constructed prior to first use with the following values:

const month jan(1);
const month feb(2);
const month mar(3);
const month apr(4);
const month may(5);
const month jun(6);
const month jul(7);
const month aug(8);
const month sep(9);
const month oct(10);
const month nov(11);
const month dec(12);
Class year
namespace std {
namespace chrono {
  class year
  {
  public:
    explicit year(signed-integral-with-range-greater-than-the-range-of-year y);
    operator int() const noexcept;

    year() = delete;
    year(const year&) = default;
    year& operator=(const year&) = default;
    ~year() = default;
  };
}
}

The class year is used to specify the year when constructing a date. It also defines the range of the date class by restricting the value of the year to a range. That range shall be at least [year(-32767)/jan/1 thru year(32767)/dec/31].

explicit year(signed-integral-with-range-greater-than-the-range-of-year y);

Effects: Constructs an object of class year by storing y.

Postconditions: static_cast<int>(*this) == y

Throws: if y is outside of the supported range, throws an exception of type bad_date.

In order for year to detect overflow, the integral type used to construct the year must have a range greater than that of year.

operator int() const noexcept;

Returns: the value of the stored int.

Class weekday
namespace std {
namespace chrono {
  class weekday
  {
  public:
    explicit weekday(int wd);
    operator int() const noexcept;

    day operator[](decltype(_1st) d) const noexcept;
    day operator[](int n) const;

    weekday() = delete;
    weekday(const weekday&) = default;
    weekday& operator=(const weekday&) = default;
    ~weekday() = default;
  };

  extern const weekday sun;
  extern const weekday mon;
  extern const weekday tue;
  extern const weekday wed;
  extern const weekday thu;
  extern const weekday fri;
  extern const weekday sat;
}
}

The class weekday is used to specify a day of the week.

explicit weekday(int wd);

Effects: Constructs an object of class week_day by storing wd.

Postconditions: static_cast<int>(*this) == wd

Throws: if wd is outside of the range [0, 6], throws an exception of type bad_date.

operator int() const noexcept;

Returns: the value of the stored int.

day operator[](decltype(_1st) d) const noexcept;

Returns: A day with copies of d and *this stored within. If this day is converted to an int the value of that int is unspecified.

day operator[](int n) const;

Returns: A day with copies of n and *this stored within. If this day is converted to an int the value of that int is unspecified.

Throws: if n is outside of the range [1, 5], throws an exception of type bad_date.

These const weekday objects are constructed prior to first use with the following values:

const weekday sun(0);
const weekday mon(1);
const weekday tue(2);
const weekday wed(3);
const weekday thu(4);
const weekday fri(5);
const weekday sat(6);
Class year_month
namespace std {
namespace chrono {
  class year_month
  {
  public:
    year_month() = delete;
    year_month(const year_month&) = default;
    year_month& operator=(const year_month&) = default;
    ~year_month() = default;
  };
}
}

Class year_month has no public member API except for being CopyConstructible, CopyAssignable, and Destructible. It is capable of storing both a year and a month.

Class month_day
namespace std {
namespace chrono {
  class month_day
  {
  public:
    month_day() = delete;
    month_day(const month_day&) = default;
    month_day& operator=(const month_day&) = default;
    ~month_day() = default;
  };
}
}

Class month_day has no public member API except for being CopyConstructible, CopyAssignable, and Destructible. It is capable of storing both a month and a day.

Date generation functions

namespace std {
namespace chrono {
  month_day operator/(day, month) noexcept;
  month_day operator/(month, day) noexcept;
  
  year_month operator/(year, month) noexcept;
  
  date operator/(year_month, day);
  date operator/(month_day, year);
  date operator/(year_month, int);
  date operator/(month_day, int);
}
}

These operators are used to construct objects of type day, month_day, year_month, and date. [Note: In each case, these operators are the only public way to construct the returned objects with the supplied arguments. Implementations will access unspecified constructors to return the specified value. — end note]

month_day operator/(day d, month m) noexcept;
month_day operator/(month m, day d) noexcept;

Returns: A month_day with copies of d and m stored within.

year_month operator/(year y, month m) noexcept;

Returns: A year_month with copies of y and m stored within.

date operator/(year_month ym, day d);
date operator/(month_day md, year y);

Returns: A date constructed using the year, month, day stored in the arguments as follows. [Note: A date is not constructible by these arguments except via these operators. Implementations will construct the date via an unspecified interface. — end note]

Let d represent both the day argument, and the day stored within md. Let m represent both the month stored within ym, and the month stored within md. Let y represent both the year argument, and the year stored within ym. The symbols y_, m_, d_, and meta_ refer to the exposition-only private data members of date in [class.date].

The meta-data stored in the date is that the date is one of 3 forms:

  1. nth day of the month [1-31]
  2. last day of the month
  3. nth weekday of the month, where n may be [1-5] or last

The third form must store n, which can take on 6 values (counting last), and the weekday which can take on 7 values. That combines to 42 possible values which can be stored in 6 bits. Six bits can store up to 64 values, and some of those values not used for storing the 42 states associated with form 3 can go towards indicating which form the date is. The example implementation uses two 3-bit fields to store this data.

date operator/(year_month ym, int d);

Returns: ym / day(d).

date operator/(month_day md, int y);

Returns: md / year(y).

Date durations

There are three chrono date duration types: days, months and years. days is a chrono::duration The latter two types are modeled after chrono::duration except that there are no conversions to and from the the different durations, not even amongst themselves. These durations represent time durations, as opposed to representing a date component (e.g. 7 months) and are used in date arithmetic. These durations are based on a signed integral type that must be at least 32 bits.

In the example implementation months and years are implemented as a typdef to an "unnamed" template specialization. I.e. they differ in type only, not in functionality.

Class months
namespace std {
namespace chrono {
  class months
  {
  public:
      typedef signed-integral-type-of-at-least-32-bits rep;
      months() = default;
      explicit months(rep x) noexcept;
  
      rep count() const noexcept;
  
      months operator+() const noexcept;
      months operator-() const noexcept;
  
      months& operator++() noexcept;
      months  operator++(int) noexcept;
      months& operator--() noexcept;
      months  operator--(int) noexcept;
  
      months& operator+=(const months& x) noexcept;
      months& operator-=(const months& x) noexcept;
  
      months& operator*=(const rep& rhs) noexcept;
      months& operator/=(const rep& rhs) noexcept;
      months& operator%=(const rep& rhs) noexcept;
      months& operator%=(const months& rhs) noexcept;
  private:
      rep x_;  // exposition only
  };

  months operator+(months x, months y) noexcept;
  months operator-(months x, months y) noexcept;
  months operator*(months x, months::rep y) noexcept;
  months operator*(months::rep x, months y) noexcept;
  months operator/(months x, months::rep y) noexcept;
  months::rep operator/(months x, months y) noexcept;
  months operator%(months x, months::rep y) noexcept;
  months operator%(months x, months y) noexcept;
  
  bool operator==(months x, months y) noexcept;
  bool operator!=(months x, months y) noexcept;
  bool operator< (months x, months y) noexcept;
  bool operator> (months x, months y) noexcept;
  bool operator<=(months x, months y) noexcept;
  bool operator>=(months x, months y) noexcept;
}
}
months(rep x) noexcept;

Effects: Constructs a months.

Postconditions: count() == x.

rep count() const noexcept;

Returns: x_.

months operator+() const noexcept;

Returns: months(x_).

months operator-() const noexcept;

Returns: months(-x_).

months& operator++() noexcept;

Effects: ++x_.

Returns: *this.

months  operator++(int) noexcept;

Returns: months(x_++).

months& operator--() noexcept;

Effects: --x_.

Returns: *this.

months  operator--(int) noexcept;

Returns: months(x_--).

months& operator+=(const months& x) noexcept;

Effects: x_ += x.count().

Returns: *this.

months& operator-=(const months& x) noexcept;

Effects: x_ -= x.count().

Returns: *this.

months& operator*=(const rep& rhs) noexcept;

Effects: x_ *= rhs.

Returns: *this.

months& operator/=(const rep& rhs) noexcept;

Effects: x_ /= rhs.

Returns: *this.

months& operator%=(const rep& rhs) noexcept;

Effects: x_ %= rhs.

Returns: *this.

months& operator%=(const months& rhs) noexcept;

Effects: x_ %= rhs.count().

Returns: *this.

months operator+(months x, months y) noexcept;

Returns: months(x.count() + y.count()).

months operator-(months x, months y) noexcept;

Returns: months(x.count() - y.count()).

months operator*(months x, months::rep y) noexcept;

Returns: months(x.count() * y).

months operator*(months::rep x, months y) noexcept;

Returns: months(x * y.count()).

months operator/(months x, months::rep y) noexcept;

Returns: months(x.count() / y).

months::rep operator/(months x, months y) noexcept;

Returns: x.count() / y.count().

months operator%(months x, months::rep y) noexcept;

Returns: months(x.count() % y).

months operator%(months x, months y) noexcept;

Returns: months(x.count() % y.count()).

bool operator==(months x, months y) noexcept;

Returns: x.count() == y.count().

bool operator!=(months x, months y) noexcept;

Returns: !(x == y).

bool operator< (months x, months y) noexcept;

Returns: x.count() < y.count().

bool operator> (months x, months y) noexcept;

Returns: y < x.

bool operator<=(months x, months y) noexcept;

Returns: !(y < x).

bool operator>=(months x, months y) noexcept;

Returns: !(x < y).

Class years
namespace std {
namespace chrono {
  class years
  {
  public:
      typedef signed-integral-type-of-at-least-32-bits rep;
      years() = default;
      explicit years(rep x) noexcept;
  
      rep count() const noexcept;
  
      years operator+() const noexcept;
      years operator-() const noexcept;
  
      years& operator++() noexcept;
      years  operator++(int) noexcept;
      years& operator--() noexcept;
      years  operator--(int) noexcept;
  
      years& operator+=(const years& x) noexcept;
      years& operator-=(const years& x) noexcept;
  
      years& operator*=(const rep& rhs) noexcept;
      years& operator/=(const rep& rhs) noexcept;
      years& operator%=(const rep& rhs) noexcept;
      years& operator%=(const years& rhs) noexcept;
  private:
      rep x_;  // exposition only
  };

  years operator+(years x, years y) noexcept;
  years operator-(years x, years y) noexcept;
  years operator*(years x, years::rep y) noexcept;
  years operator*(years::rep x, years y) noexcept;
  years operator/(years x, years::rep y) noexcept;
  years::rep operator/(years x, years y) noexcept;
  years operator%(years x, years::rep y) noexcept;
  years operator%(years x, years y) noexcept;
  
  bool operator==(years x, years y) noexcept;
  bool operator!=(years x, years y) noexcept;
  bool operator< (years x, years y) noexcept;
  bool operator> (years x, years y) noexcept;
  bool operator<=(years x, years y) noexcept;
  bool operator>=(years x, years y) noexcept;
}
}
years(rep x) noexcept;

Effects: Constructs a years.

Postconditions: count() == x.

rep count() const noexcept;

Returns: x_.

years operator+() const noexcept;

Returns: years(x_).

years operator-() const noexcept;

Returns: years(-x_).

years& operator++() noexcept;

Effects: ++x_.

Returns: *this.

years  operator++(int) noexcept;

Returns: years(x_++).

years& operator--() noexcept;

Effects: --x_.

Returns: *this.

years  operator--(int) noexcept;

Returns: years(x_--).

years& operator+=(const years& x) noexcept;

Effects: x_ += x.count().

Returns: *this.

years& operator-=(const years& x) noexcept;

Effects: x_ -= x.count().

Returns: *this.

years& operator*=(const rep& rhs) noexcept;

Effects: x_ *= rhs.

Returns: *this.

years& operator/=(const rep& rhs) noexcept;

Effects: x_ /= rhs.

Returns: *this.

years& operator%=(const rep& rhs) noexcept;

Effects: x_ %= rhs.

Returns: *this.

years& operator%=(const years& rhs) noexcept;

Effects: x_ %= rhs.count().

Returns: *this.

years operator+(years x, years y) noexcept;

Returns: years(x.count() + y.count()).

years operator-(years x, years y) noexcept;

Returns: years(x.count() - y.count()).

years operator*(years x, years::rep y) noexcept;

Returns: years(x.count() * y).

years operator*(years::rep x, years y) noexcept;

Returns: years(x * y.count()).

years operator/(years x, years::rep y) noexcept;

Returns: years(x.count() / y).

years::rep operator/(years x, years y) noexcept;

Returns: x.count() / y.count().

years operator%(years x, years::rep y) noexcept;

Returns: years(x.count() % y).

years operator%(years x, years y) noexcept;

Returns: years(x.count() % y.count()).

bool operator==(years x, years y) noexcept;

Returns: x.count() == y.count().

bool operator!=(years x, years y) noexcept;

Returns: !(x == y).

bool operator< (years x, years y) noexcept;

Returns: x.count() < y.count().

bool operator> (years x, years y) noexcept;

Returns: y < x.

bool operator<=(years x, years y) noexcept;

Returns: !(y < x).

truncated
bool operator>=(years x, years y) noexcept;

Returns: !(x < y).

Class date

namespace std {
namespace chrono {
  class date
  {
  public:
      // construction
      date() noexcept;
      static date today() noexcept;
  
      // system_clock::time_point conversions
      explicit date(chrono::system_clock::time_point tp);
      explicit operator chrono::system_clock::time_point () const;
  
      // observers
      chrono::day day() const noexcept;
      chrono::month month() const noexcept;
      chrono::year year() const noexcept;
      chrono::weekday weekday() const noexcept;
      bool is_leap_year() const noexcept;
  
      // day arithmetic
      date& operator+=(days d);
      date& operator++();
      date  operator++(int);
      date& operator-=(days d);
      date& operator--();
      date  operator--(int);
  
      // month arithmetic
      date& operator+=(months m);
      date& operator-=(months m);
  
      // year arithmetic
      date& operator+=(years y);
      date& operator-=(years y);
  private:
      short         y_;    // exposition only, stores year number
      unsigned char m_;    // exposition only, stores month number [1-12]
      unsigned char d_;    // exposition only, stores day number [1-31]
      unsigned char meta_; // exposition only, stores what date represents
  };
  
  // date comparisons
  
  bool operator==(const date& x, const date& y) noexcept;
  bool operator!=(const date& x, const date& y) noexcept;
  bool operator< (const date& x, const date& y) noexcept;
  bool operator> (const date& x, const date& y) noexcept;
  bool operator<=(const date& x, const date& y) noexcept;
  bool operator>=(const date& x, const date& y) noexcept;
  
  // date day arithmetic
  date operator+(date dt, days d);
  date operator+(days d, date dt);
  date operator-(date dt, days d);
  days operator-(date x, date y) noexcept;
  
  // date month arithmetic
  date operator+(date dt, months m);
  date operator+(months m, date dt);
  date operator-(date dt, months m);
  
  // date year arithmetic
  date operator+(date dt, years y);
  date operator+(years y, date dt);
  date operator-(date dt, years y);

  // find prior / next weekday
  date operator< (weekday wd, date x);
  date operator<=(weekday wd, date x);
  date operator> (weekday wd, date x);
  date operator>=(weekday wd, date x);
}
}

Class date represents a day in the proleptic Gregorian calendar. The year preceding year 1 is year 0, and year 0 is a leap year. The range of years representable by this class is the same as the range of year.

Construction and conversion
date() noexcept;

Effects: Constructs a date as if by year(0)/jan/1. [Note: the purpose of this constructor is to have a very efficient means of date construction when the specific value for that date is unimportant. — end note]

static date today() noexcept;

Effects: Constructs a date which represents the current day taking the local time zone into account.

explicit date(chrono::system_clock::time_point tp);

Effects: tp is converted to UTC, and then truncated to 00:00:00 hours. A date is created which reflects this point in time.

Throws: If the conversion from tp overflows the range of date, throws an exception of type bad_date.

explicit operator chrono::system_clock::time_point () const;

Returns: A chrono::system_clock::time_point which represents the date referred to by *this at 00:00:00 UTC.

Throws: If the conversion to tp overflows the range of chrono::system_clock::time_point, throws an exception of type bad_date.

date Observers
chrono::day day() const noexcept;

Returns: chrono::day(d_).

chrono::month month() const noexcept;

Returns: chrono::month(m_).

chrono::year year() const noexcept;

Returns: chrono::year(y_).

chrono::weekday weekday() const noexcept;

Returns: A weekday constructed with an int corresponding to *this date's day of the week (a value in the range of [0 - 6], 0 is Sunday).

bool is_leap_year() const noexcept;

Returns: true if year() is a leap year, and otherwise returns false.

date comparisons
bool operator==(const date& x, const date& y) noexcept;

Returns: x.year() == y.year() && x.month() == y.month() && x.day() == y.day().

bool operator!=(const date& x, const date& y) noexcept;

Returns: !(x == y).

bool operator< (const date& x, const date& y) noexcept;

Returns: x.year() < y.year() || (!(y.year() < x.year()) && (x.month() < y.month() || (!(y.month() < x.month()) && x.day() < y.day()))).

bool operator> (const date& x, const date& y) noexcept;

Returns: y < x.

bool operator<=(const date& x, const date& y) noexcept;

Returns: !(y < x).

bool operator>=(const date& x, const date& y) noexcept;

Returns: !(x < y).

date arithmetic
date& operator+=(days d);

Effects: Adds d.count() days to the current date. Sets meta_ to indicate that this date was not constructed with a day constructed from last or from an indexed weekday.

Returns: *this.

Throws: If the addition would create a date with a y_ outside of the range of year, throws an exception of type bad_date. If an exception is thrown, the state of *this is not changed.

date& operator++();

Effects: *this += days(1).

Returns: *this.

date  operator++(int);

Effects: *this += days(1).

Returns: A copy of *this prior to the increment.

date& operator-=(days d);

Effects: *this += -d

Returns: *this.

date& operator--();

Effects: *this -= days(1).

Returns: *this.

date  operator--(int);

Effects: *this -= days(1).

Returns: A copy of *this prior to the decrement.

date operator+(date dt, days d);
date operator+(days d, date dt);

Returns: dt += d.

date operator-(date dt, days d);

Returns: dt -= d.

days operator-(date x, date y) noexcept;

Returns: Computes the number of days x is ahead of y in the calendar, and returns that signed integral number n as days(n).

date& operator+=(months m);

Effects: Adds m.count() months to the current date. This is accomplished as if by storing temporary values of the date's y_, m_, d_, and meta_. Computing new values for y_ and m_ based on m. And then assigning to *this a new date constructed from the newly computed y_ and m_, and the original d_ and meta_. [Note: Thus for example if a date is constructed as the second Sunday in May, adding two months to this date results in the second Sunday in July. — end note]

Returns: *this.

Throws: If the addition would create a date with a y_ outside of the range of year, or a d_ outside the range for the newly computed y_/m_, throws an exception of type bad_date. If an exception is thrown, the state of *this is not changed.

date& operator-=(months m);

Returns: *this += -m.

date operator+(date dt, months m);
date operator+(months m, date dt);

Returns: dt += m.

date operator-(date dt, months m);

Returns: dt += -m.

date& operator+=(years y);

Effects: Adds y.count() years to the current date. This is accomplished as if by storing temporary values of the date's y_, m_, d_, and meta_. Computing a new value for y_. And then assigning to *this a new date constructed from the newly computed y_, and the original m_, d_ and meta_. [Note: Thus for example if a date is constructed as the second Sunday in May 2011, adding two years to this date results in the second Sunday in May 2013. — end note]

Returns: *this.

Throws: If the addition would create a date with a y_ outside of the range of year, or a d_ outside the range for the newly computed y_/m_, throws an exception of type bad_date. If an exception is thrown, the state of *this is not changed.

date& operator-=(years y);

Returns: *this += -y.

date operator+(date dt, years y);
date operator+(years y, date dt);

Returns: dt += y.

date operator-(date dt, years y);

Returns: dt += -y.

date operator< (weekday wd, date x);

Returns: Let a be wd converted to an int, and b be x.weekday() converted to an int. If a < b, returns x - days(b-a), else returns x - days(7 - (a-b)).

date operator<=(weekday wd, date x);

Returns: Let a be wd converted to an int, and b be x.weekday() converted to an int. If a <= b, returns x - days(b-a), else returns x - days(7 - (a-b)).

date operator> (weekday wd, date x);

Returns: Let a be wd converted to an int, and b be x.weekday() converted to an int. If a < b, returns x + days(b-a), else returns x + days(7 - (a-b)).

date operator>=(weekday wd, date x);

Returns: Let a be wd converted to an int, and b be x.weekday() converted to an int. If a <= b, returns x + days(b-a), else returns x + days(7 - (a-b)).

date I/O

Class template datepunct
namespace std {
namespace chrono {
  template <class charT>
  class datepunct
      : public locale::facet
  {
  public:
      typedef basic_string<charT> string_type;
  
      static locale::id id;
  
      explicit datepunct(size_t refs = 0);
      explicit datepunct(string_type frmt, size_t refs = 0);
  
      const string_type& fmt() const noexcept;
  private:
      string_type fmt_;  // exposition only
  };
}
}

datepunct is a facet which holds a string of formatting characters to be used with time_get ([locale.time.get]) and time_put ([locale.time.put]).

explicit datepunct(size_t refs = 0);

Effects: Constructs a datepunct by constructing the base class with refs.

Postconditions: fmt() == "%F".

explicit datepunct(string_type frmt, size_t refs = 0);

Effects: Constructs a datepunct by constructing the base class with refs.

Postconditions: fmt() == frmt.

const string_type& fmt() const noexcept;

Returns: fmt_.

date_fmt Manipulator
template<class charT>
unspecified
date_fmt(basic_string<charT> fmt);

template<class charT>
unspecified
date_fmt(const charT* fmt);

Returns: An object of unspecified type such that if out is an object of type basic_ostream<charT, traits> then the expression out << date_fmt(fmt) behaves as if it called f(out, fmt), or if in is an object of type basic_istream<charT, traits> then the expression in >> date_fmt(fmt) behaves as if it called f(in, fmt), where the function f is defined as:

template<class charT, class traits>
void f(basic_ios<charT, traits>& str, basic_string<charT> fmt)
{
    str.imbue(locale(str.getloc(), new datepunct<charT>(move(fmt))));
}
date inserter and extractor
template<class charT, class Traits>
basic_istream<charT,Traits>&
operator>>(basic_istream<charT,Traits>& is, date& d);

Effects: Behaves as a formatted input function ([istream.formatted.reqmts]). After constructing a sentry object, if the sentry converts to true, acquires the time_get facet from the stream's locale. If the locale has a datepunct facet, obtains the conversion specifier string from that facet, otherwise sets the conversion specifier string to "%F". Then extracts a tm from is as if:

tg.get(is, 0, is, err, &t, fmt, fmtend);

Where tg is the time_get facet, err is a local variable of type ios_base::iostate, t is a local variable of type tm, and [fmt, fmtend) are local variables of type charT* which delimit the conversion specifier string.

If (err & ios_base::failbit) is false, assigns a date to d which is constructed from the year, month and day information stored within t. In any case, then calls is.setstate(err).

Returns: is.

template<class charT, class Traits>
basic_ostream<charT, Traits>&
operator<<(basic_ostream<charT, Traits>& os, const date& d);

Effects: Behaves as a formatted output function ([ostream.formatted.reqmts]). After constructing a sentry object, if the sentry converts to true, acquires the time_put facet from the stream's locale. If the locale has a datepunct facet, obtains the formatting string from that facet, otherwise sets the formatting string to "%F". Then creates a local variable t of type tm. The variable t is filled with the year, month, day and weekday information contained in d. t is then inserted into os as if:

bool failed = tp.put(os, os, os.fill(), &t, pattern, pat_end).failed();

Where tp is the time_put facet, and [pattern, pat_end) are local variables of type charT* which delimit the formatting string.

If failed is true, calls os.setstate(ios_base::failbit | ios_base::badbit).

Returns: os.

Acknowledgements

I'm very grateful for the thorough review given by Daniel Krügler.