Really new answer to a really old question!
Using this free and open source library, and a C++14 compiler (such as clang) I can now write this:
#include "date.h"
constexpr
date::year_month_day
add(date::year_month_day ymd, date::months m) noexcept
{
using namespace date;
auto was_last = ymd == ymd.year()/ymd.month()/last;
ymd = ymd + m;
if (!ymd.ok() || was_last)
ymd = ymd.year()/ymd.month()/last;
return ymd;
}
int
main()
{
using namespace date;
static_assert(add(30_d/01/2009, months{ 1}) == 28_d/02/2009, "");
static_assert(add(31_d/01/2009, months{ 1}) == 28_d/02/2009, "");
static_assert(add(27_d/02/2009, months{ 1}) == 27_d/03/2009, "");
static_assert(add(28_d/02/2009, months{ 1}) == 31_d/03/2009, "");
static_assert(add(31_d/01/2009, months{50}) == 31_d/03/2013, "");
}
And it compiles.
Note the remarkable similarity between the actual code, and the OP's pseudo-code:
30/01/2009 + 1 month = 28/02/2009
31/01/2009 + 1 month = 28/02/2009
27/02/2009 + 1 month = 27/03/2009
28/02/2009 + 1 month = 31/03/2009
31/01/2009 + 50 months = 31/03/2013
Also note that compile-time information in leads to compile-time information out.
C++20 <chrono>
update:
Your C++ vendors are starting to ship C++20 <chrono>
which can do this without a 3rd party library with nearly identical syntax.
Also note the somewhat unusual rules the OP requires for adding months, which is easily implementable in <chrono>
:
If the resultant month overflows the day field or if the input month is the last day of the month, then snap the result to the end of the month.
#include <chrono>
constexpr
std::chrono::year_month_day
add(std::chrono::year_month_day ymd, std::chrono::months m) noexcept
{
using namespace std::chrono;
auto was_last = ymd == ymd.year()/ymd.month()/last;
ymd = ymd + m;
if (!ymd.ok() || was_last)
ymd = ymd.year()/ymd.month()/last;
return ymd;
}
int
main()
{
using namespace std::chrono;
static_assert(add(30d/01/2009, months{ 1}) == 28d/02/2009);
static_assert(add(31d/01/2009, months{ 1}) == 28d/02/2009);
static_assert(add(27d/02/2009, months{ 1}) == 27d/03/2009);
static_assert(add(28d/02/2009, months{ 1}) == 31d/03/2009);
static_assert(add(31d/01/2009, months{50}) == 31d/03/2013);
}
Demo.
30/1/2009 +1 month - 1 month = 28/1/2009
. This seems like a bad way to define months. – Allograph