chrono::date
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
ConstructionSignificant 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 date
s is safe, easy, intuitive, and efficient.
date
ArithmeticDate 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.
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
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:
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) = 4chrono, implementation 1:
1.80 microseconds sizeof(date) = 8chrono, implementation 2:
2.64 microseconds sizeof(date) = 4chrono, 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.
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 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. date
s 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.
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 tod
that haswd
as itsweekday
.wd <= d
Returns the first date
on or prior tod
that haswd
as itsweekday
.wd > d
Returns the first date
afterd
that haswd
as itsweekday
.wd >= d
Returns the first date
on or afterd
that haswd
as itsweekday
.
date
I/O
date
s 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 date
s
from an istream
.
To demonstrate the ease with which date
s 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
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.
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:
ISO 8601 gives two examples:
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)); }
y
.
date
7 days
for each full week,
taking into account that the week numbers start at 1, not 0.
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:
start + days(3)
could have been used in place of thu >
start
. But the ISO rules are worded in terms of Thursday, and this
library provides a natural translation from English to C++.
year
and
weekday
to int
is helpful, intuitive and safe in
this example code. These conversions are so natural that they are not even noticed
unless you're looking for them.
<date>
library.
date_to_week
return a tuple
? Should
week_to_date
take a tuple
? Are these the best names
for these functions? etc.julian
example?)Text formatted like this is intended to be rationale for some of the proposed wording. It is not part of the proposed wording.
This subclause describes the chrono calendar library which provides date generation and date arithmetic facilities.
<date>
synopsisThere is an obvious lack of use ofconstexpr
below. This is simply because I currently lack a compiler to test on which implementsconstexpr
, 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 usesconstexpr
. I fully support and embrace the use ofconstexpr
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
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
.
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.
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 storingd
.Postconditions:
static_cast<int>(*this) == d
Throws: if
d
is outside of the range [1, 31], throws an exception of typebad_date
.
day(decltype(_1st) d) noexcept;
Effects: Constructs an object of class
day
by storingd
.Postconditions: The value returned by casting
*this
toint
depends on the value ofd
as follows:
_1st
: returns1
._2nd
: returns2
._3rd
: returns3
._4th
: returns4
._5th
: returns5
.last
: The value returned is unspecified. Other parts of thechrono
library will need to recognize aday
constructed with this value.
operator int() const noexcept;
Returns: If
*this
has a value that resulted from constructing aday
from anint
, returns the value of thatint
. 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
.
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 storingm
.Postconditions:
static_cast<int>(*this) == m
Throws: if
m
is outside of the range [1, 12], throws an exception of typebad_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);
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 storingy
.Postconditions:
static_cast<int>(*this) == y
Throws: if
y
is outside of the supported range, throws an exception of typebad_date
.
In order for
year
to detect overflow, the integral type used to construct theyear
must have a range greater than that ofyear
.
operator int() const noexcept;
Returns: the value of the stored
int
.
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 storingwd
.Postconditions:
static_cast<int>(*this) == wd
Throws: if
wd
is outside of the range [0, 6], throws an exception of typebad_date
.
operator int() const noexcept;
Returns: the value of the stored
int
.
day operator[](decltype(_1st) d) const noexcept;
Returns: A
day
with copies ofd
and*this
stored within. If thisday
is converted to anint
the value of thatint
is unspecified.
day operator[](int n) const;
Returns: A
day
with copies ofn
and*this
stored within. If thisday
is converted to anint
the value of thatint
is unspecified.Throws: if
n
is outside of the range [1, 5], throws an exception of typebad_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);
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
.
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
.
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 ofd
andm
stored within.
year_month operator/(year y, month m) noexcept;
Returns: A
year_month
with copies ofy
andm
stored within.
date operator/(year_month ym, day d); date operator/(month_day md, year y);
Returns: A
date
constructed using theyear
,month
,day
stored in the arguments as follows. [Note: Adate
is not constructible by these arguments except via these operators. Implementations will construct thedate
via an unspecified interface. — end note]Let
d
represent both theday
argument, and theday
stored withinmd
. Letm
represent both themonth
stored withinym
, and themonth
stored withinmd
. Lety
represent both theyear
argument, and theyear
stored withinym
. The symbolsy_
,m_
,d_
, andmeta_
refer to the exposition-only private data members ofdate
in [class.date].
- If
d
was constructed with_1st
,_2nd
,_3rd
,_4th
, or_5th
, the construction has the same behavior as if theday
was constructed with the integers 1 through 5 respectively.- If
d
was constructed withlast
, then constructs adate
withy_ = y
,m_ = m
, andd_ =
to the last day of the monthm
for the indicated yeary
, and stores withinmeta_
the fact that thisdate
represents the last day of the month for purposes of month and year-oriented arithmetic.- If
d
was constructed by indexing aweekday
with_1st
through_5th
, or equivalently by 1 through 5, then constructs adate
corresponding to the nth day of the week for the indicated monthm
and yeary
. If the index is 5 or_5th
, and the computed day exceeds the end of the month, an exception of typebad_date
is thrown. Stores withinmeta_
the fact that thisdate
represents the nth weekday of the month for purposes of month and year-oriented arithmetic.- If
d
was constructed by indexing aweekday
withlast
, then constructs adate
corresponding to the last day of the week for the indicated monthm
and yeary
. Stores within themeta_
the fact that thisdate
represents the last weekday of the month for purposes of month and year-oriented arithmetic.- Else
d
was constructed with an integral value in the range [1, 31]. If the value stored ind
is outside the range of valid dates for this monthm
and yeary
, throws an exception of typebad_date
. Else constructs adate
for whichy_ = y
,m_ = m
,d_ = d
, andmeta_
indicates that thisdate
is of the simple form yyyy-mm-dd.The meta-data stored in the
date
is that thedate
is one of 3 forms:
- nth day of the month [1-31]
- last day of the month
- 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)
.
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
andyears
are implemented as atypdef
to an "unnamed" template specialization. I.e. they differ in type only, not in functionality.
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)
.
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)
.
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
.
date() noexcept;
Effects: Constructs a
date
as if byyear(0)/jan/1
. [Note: the purpose of this constructor is to have a very efficient means ofdate
construction when the specific value for thatdate
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. Adate
is created which reflects this point in time.Throws: If the conversion from
tp
overflows the range ofdate
, throws an exception of typebad_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 ofchrono::system_clock::time_point
, throws an exception of typebad_date
.
date
Observerschrono::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 anint
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
ifyear()
is a leap year, and otherwise returnsfalse
.
date
comparisonsbool 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
arithmeticdate& operator+=(days d);
Effects: Adds
d.count()
days to the currentdate
. Setsmeta_
to indicate that thisdate
was not constructed with aday
constructed fromlast
or from an indexedweekday
.Returns:
*this
.Throws: If the addition would create a date with a
y_
outside of the range ofyear
, throws an exception of typebad_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 ofy
in the calendar, and returns that signed integral numbern
asdays(n)
.
date& operator+=(months m);
Effects: Adds
m.count()
months to the currentdate
. This is accomplished as if by storing temporary values of thedate
'sy_
,m_
,d_
, andmeta_
. Computing new values fory_
andm_
based onm
. And then assigning to*this
a newdate
constructed from the newly computedy_
andm_
, and the originald_
andmeta_
. [Note: Thus for example if adate
is constructed as the second Sunday in May, adding twomonths
to thisdate
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 ofyear
, or ad_
outside the range for the newly computedy_/m_
, throws an exception of typebad_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 currentdate
. This is accomplished as if by storing temporary values of thedate
'sy_
,m_
,d_
, andmeta_
. Computing a new value fory_
. And then assigning to*this
a newdate
constructed from the newly computedy_
, and the originalm_
,d_
andmeta_
. [Note: Thus for example if adate
is constructed as the second Sunday in May 2011, adding twoyears
to thisdate
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 ofyear
, or ad_
outside the range for the newly computedy_/m_
, throws an exception of typebad_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
bewd
converted to anint
, andb
bex.weekday()
converted to anint
. Ifa < b
, returnsx - days(b-a)
, else returnsx - days(7 - (a-b))
.
date operator<=(weekday wd, date x);
Returns: Let
a
bewd
converted to anint
, andb
bex.weekday()
converted to anint
. Ifa <= b
, returnsx - days(b-a)
, else returnsx - days(7 - (a-b))
.
date operator> (weekday wd, date x);
Returns: Let
a
bewd
converted to anint
, andb
bex.weekday()
converted to anint
. Ifa < b
, returnsx + days(b-a)
, else returnsx + days(7 - (a-b))
.
date operator>=(weekday wd, date x);
Returns: Let
a
bewd
converted to anint
, andb
bex.weekday()
converted to anint
. Ifa <= b
, returnsx + days(b-a)
, else returnsx + days(7 - (a-b))
.
date
I/Odatepunct
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 withrefs
.Postconditions:
fmt() == "%F"
.
explicit datepunct(string_type frmt, size_t refs = 0);
Effects: Constructs a
datepunct
by constructing the base class withrefs
.Postconditions:
fmt() == frmt
.
const string_type& fmt() const noexcept;
Returns:
fmt_
.
date_fmt
Manipulatortemplate<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 typebasic_ostream<charT, traits>
then the expressionout << date_fmt(fmt)
behaves as if it calledf(out, fmt)
, or ifin
is an object of typebasic_istream<charT, traits>
then the expressionin >> date_fmt(fmt)
behaves as if it calledf(in, fmt)
, where the functionf
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 extractortemplate<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 thetime_get
facet from the stream'slocale
. If thelocale
has adatepunct
facet, obtains the conversion specifier string from that facet, otherwise sets the conversion specifier string to "%F". Then extracts atm
fromis
as if:tg.get(is, 0, is, err, &t, fmt, fmtend);Where
tg
is thetime_get
facet,err
is a local variable of typeios_base::iostate
,t
is a local variable of typetm
, and[fmt, fmtend)
are local variables of typecharT*
which delimit the conversion specifier string.If
(err & ios_base::failbit)
is false, assigns adate
tod
which is constructed from the year, month and day information stored withint
. In any case, then callsis.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 thetime_put
facet from the stream'slocale
. If thelocale
has adatepunct
facet, obtains the formatting string from that facet, otherwise sets the formatting string to "%F". Then creates a local variablet
of typetm
. The variablet
is filled with the year, month, day and weekday information contained ind
.t
is then inserted intoos
as if:bool failed = tp.put(os, os, os.fill(), &t, pattern, pat_end).failed();Where
tp
is thetime_put
facet, and[pattern, pat_end)
are local variables of typecharT*
which delimit the formatting string.If
failed
is true, callsos.setstate(ios_base::failbit | ios_base::badbit)
.Returns:
os
.
I'm very grateful for the thorough review given by Daniel Krügler.