Given the new <chrono>
facilities in C++20, how do I add some number of days (say n
) to a date?
When I try this I get a compile-time error:
auto d = July/4/2020;
auto d2 = d + days{5};
~ ^ ~~~~~~~
error: invalid operands to binary expression
('std::chrono::year_month_day' and 'std::chrono::days')
A "date" in <chrono>
is just a time point with days-precision. And a
"date-time" is just a chrono::time_point
(usually based on
system_clock
) with precision finer than days
. And the canonical "date type" in
<chrono>
is:
chrono::time_point<chrono::system_clock, chrono::days>
This is nothing but a days
-precision time_point
based on
system_clock
. This particular time_point
also has a convenience
type alias: sys_days
.
using sys_days = time_point<system_clock, days>;
So to add a number of days to sys_days
one just does:
sys_days tp = ...;
tp += days{n};
// or
auto tp2 = tp + days{n};
If it is just one day, you can also just:
++tp;
This is very efficient because under the hood it is just adding two
int
s.
If your time_point
has precision finer than days
, it is still the
same procedure to add days
to it:
auto tp = system_clock::now() + days{n};
When I try this I get a compile-time error:
auto d = July/4/2020; auto d2 = d + days{5}; ~ ^ ~~~~~~~ error: invalid operands to binary expression ('std::chrono::year_month_day' and 'std::chrono::days')
The variable d
above has type year_month_day
. Even though year_month_day
is semantically equivalent to sys_days
,
it does not directly support days
-precision arithmetic.
year_month_day
is a days-precision time point (but not a
chrono::time_point
). Instead it is a {year, month, day}
data
structure. You can easily perform days-precision arithmetic by
converting it to sys_days
first:
auto d = July/4/2020;
auto d2 = sys_days{d} + days{5};
In this case, the type of the result d2
is sys_days
. If you would like the
result to have year_month_day
, then just convert it back:
year_month_day d2 = sys_days{d} + days{5};
Rationale for this design:
Efficiency. The conversion from year_month_day
to sys_days
(and
back) takes a bit of number crunching. It isn't a huge amount. But
it is large compared to a single integral addition.
If the <chrono>
library provided days
-precision arithmetic for year_month_day
,
the algorithm would be to convert the year_month_day
to sys_days
,
do the arithmetic, and then convert the result back to
year_month_day
. If you have a lot of days
-precision arithmetic to
do, it is better to just traffic in sys_days
, and convert to
year_month_day
only when necessary (i.e. when you need to get the
year
, month
or day
fields out of it).
If the library provided the day-precision arithmetic for year_month_day
,
clients would blindly use it, not realizing that year_month_day
is the
wrong data structure for their application. It would be akin to giving
std::list
an indexing operator (which would be quite easy to do):
template <class T, class A>
T&
list<T, A>::operator[](size_type i)
{
return *advance(begin(), i);
}
Providing such an API just makes it too easy to write inefficient
code. sys_days
is the data structure of choice for doing days
precision arithmetic.
It still doesn't work for me:
auto d = July/4/2020; auto d2 = sys_days{d} + day{5}; ~~~~~~~~~~~ ^ ~~~~~~ error: invalid operands to binary expression ('std::chrono::sys_days' and 'std::chrono::day')
You need to use days
, not day
. day
is a calendrical specifier
for the day of a month in the civil calendar. days
is a
chrono::duration
. See this stack overflow Q/A for a more in-depth
discussion about the distinction between these two concepts.
If I have a
year_month_day
and I want to add a few days to it that I know won't reach the end of the month, can I do that without converting tosys_days
and thus gain efficiency?
Yes.
auto d = July/4/2020;
auto d2 = d.year()/d.month()/(d.day() + days{5});
The above simply adds 5 to the day
field of d
. There is no
checking to see if the result goes past the last day of the month. If
it does go past the last day of the month, that result will be stored
in the day field (up to day 255), and d2.ok()
will return false
.
year_month_day
is good for retrieving the year
, month
or day
fields from a date. It is also good for years
and months
precision calendrical arithmetic. sys_days
is good for days
precision arithmetic, and for converting to a finer precision
(a date-time).
year_month_day
and sys_days
can convert to one another with no
information loss. Use whichever data structure makes the most sense.
And if you forget, the compiler will remind you, just like it does for
vector
(no push_front
), list
(no indexing operator), and
forward_list
(no size
).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With