Skip to content

Commit

Permalink
[libc++][chrono] Fixes year_month year wrapping. (#74938)
Browse files Browse the repository at this point in the history
Adding months to a year_month should wrap the year when the month
becomes greater than twelve or less than one.

This fixes the issue for year_month. Other classes with a year and month
do not have this issue. This has been verified and tests are added to
avoid possible regressions.

Also fixes some variable copy-paste errors in the tests.

Fixes llvm/llvm-project#73162

NOKEYCHECK=True
GitOrigin-RevId: 766bf140ffd6ce420426b80aa2cce0cb0d25ab7d
  • Loading branch information
mordante authored and copybara-github committed Dec 12, 2023
1 parent 2b49c8a commit 3d39d2a
Show file tree
Hide file tree
Showing 15 changed files with 329 additions and 161 deletions.
28 changes: 24 additions & 4 deletions include/__chrono/year_month.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ class year_month {
: __y_{__yval}, __m_{__mval} {}
_LIBCPP_HIDE_FROM_ABI inline constexpr chrono::year year() const noexcept { return __y_; }
_LIBCPP_HIDE_FROM_ABI inline constexpr chrono::month month() const noexcept { return __m_; }
_LIBCPP_HIDE_FROM_ABI inline constexpr year_month& operator+=(const months& __dm) noexcept { this->__m_ += __dm; return *this; }
_LIBCPP_HIDE_FROM_ABI inline constexpr year_month& operator-=(const months& __dm) noexcept { this->__m_ -= __dm; return *this; }
_LIBCPP_HIDE_FROM_ABI inline constexpr year_month& operator+=(const years& __dy) noexcept { this->__y_ += __dy; return *this; }
_LIBCPP_HIDE_FROM_ABI inline constexpr year_month& operator-=(const years& __dy) noexcept { this->__y_ -= __dy; return *this; }
_LIBCPP_HIDE_FROM_ABI inline constexpr year_month& operator+=(const months& __dm) noexcept;
_LIBCPP_HIDE_FROM_ABI inline constexpr year_month& operator-=(const months& __dm) noexcept;
_LIBCPP_HIDE_FROM_ABI inline constexpr year_month& operator+=(const years& __dy) noexcept;
_LIBCPP_HIDE_FROM_ABI inline constexpr year_month& operator-=(const years& __dy) noexcept;
_LIBCPP_HIDE_FROM_ABI inline constexpr bool ok() const noexcept { return __y_.ok() && __m_.ok(); }
};

Expand Down Expand Up @@ -92,6 +92,26 @@ _LIBCPP_HIDE_FROM_ABI constexpr
year_month operator-(const year_month& __lhs, const years& __rhs) noexcept
{ return __lhs + -__rhs; }

_LIBCPP_HIDE_FROM_ABI inline constexpr year_month& year_month::operator+=(const months& __dm) noexcept {
*this = *this + __dm;
return *this;
}

_LIBCPP_HIDE_FROM_ABI inline constexpr year_month& year_month::operator-=(const months& __dm) noexcept {
*this = *this - __dm;
return *this;
}

_LIBCPP_HIDE_FROM_ABI inline constexpr year_month& year_month::operator+=(const years& __dy) noexcept {
*this = *this + __dy;
return *this;
}

_LIBCPP_HIDE_FROM_ABI inline constexpr year_month& year_month::operator-=(const years& __dy) noexcept {
*this = *this - __dy;
return *this;
}

} // namespace chrono

_LIBCPP_END_NAMESPACE_STD
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ constexpr bool test() {
assert(ym.year() == y);
}

{ // Test year wrapping
year_month ym{year{2020}, month{4}};

ym += months{12};
assert((ym == year_month{year{2021}, month{4}}));

ym -= months{12};
assert((ym == year_month{year{2020}, month{4}}));
}

return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,17 @@ constexpr bool test() {
{ // year_month - months

year_month ym{year{1234}, std::chrono::November};
for (int i = 0; i <= 10; ++i) // TODO test wrap-around
{
for (int i = 0; i <= 10; ++i) {
year_month ym1 = ym - months{i};
assert(static_cast<int>(ym1.year()) == 1234);
assert(ym1.month() == month(11 - i));
}
// Test the year wraps around.
for (int i = 12; i <= 15; ++i) {
year_month ym1 = ym - months{i};
assert(static_cast<int>(ym1.year()) == 1233);
assert(ym1.month() == month(11 - i + 12));
}
}

{ // year_month - year_month
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,19 @@

#include "test_macros.h"

using year = std::chrono::year;
using years = std::chrono::years;
using month = std::chrono::month;
using months = std::chrono::months;
using year = std::chrono::year;
using years = std::chrono::years;
using month = std::chrono::month;
using months = std::chrono::months;
using year_month = std::chrono::year_month;

// year_month + years
constexpr bool test_ym_plus_y() {
ASSERT_NOEXCEPT(std::declval<year_month>() + std::declval<years>());
ASSERT_NOEXCEPT(std::declval<years>() + std::declval<year_month>());

ASSERT_SAME_TYPE(
year_month, decltype(std::declval<year_month>() + std::declval<years>()));
ASSERT_SAME_TYPE(
year_month, decltype(std::declval<years>() + std::declval<year_month>()));
ASSERT_SAME_TYPE(year_month, decltype(std::declval<year_month>() + std::declval<years>()));
ASSERT_SAME_TYPE(year_month, decltype(std::declval<years>() + std::declval<year_month>()));

year_month ym{year{1234}, std::chrono::January};
for (int i = 0; i <= 10; ++i) {
Expand All @@ -64,10 +62,17 @@ constexpr bool test_ym_plus_m() {
ASSERT_NOEXCEPT(std::declval<year_month>() + std::declval<months>());
ASSERT_NOEXCEPT(std::declval<months>() + std::declval<year_month>());

ASSERT_SAME_TYPE(year_month, decltype(std::declval<year_month>() +
std::declval<months>()));
ASSERT_SAME_TYPE(year_month, decltype(std::declval<months>() +
std::declval<year_month>()));
ASSERT_SAME_TYPE(year_month, decltype(std::declval<year_month>() + std::declval<months>()));
ASSERT_SAME_TYPE(year_month, decltype(std::declval<months>() + std::declval<year_month>()));

{
// [time.cal.ym.nonmembers]/4
// Returns: A year_month value z such that z.ok() && z - ym == dm is true.
year_month ym = {year{1234}, std::chrono::January};
months dm = months{42};
year_month z = ym + dm;
assert(z.ok() && z - ym == dm);
}

year_month ym{year{1234}, std::chrono::January};
for (int i = 0; i <= 11; ++i) {
Expand All @@ -89,7 +94,6 @@ constexpr bool test_ym_plus_m() {
assert(ym2.month() == month(1 + i % 12));
assert(ym1 == ym2);
}

return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,40 @@ constexpr bool test() {
assert(static_cast<unsigned>((ymd).month()) == i + 1);
}

{ // Validate the ok status when the day is not present in the new month.
year_month_day ymd{year{2020}, month{3}, day{31}};
ymd += months{1};
assert((ymd == year_month_day{year{2020}, month{4}, day{31}}));
assert(!ymd.ok());

ymd -= months{1};
assert((ymd == year_month_day{year{2020}, month{3}, day{31}}));
assert(ymd.ok());
}

{ // Validate the ok status when the day becomes present in the new month.
year_month_day ymd{year{2020}, month{4}, day{31}};
assert(!ymd.ok());

ymd += months{1};
assert((ymd == year_month_day{year{2020}, month{5}, day{31}}));
assert(ymd.ok());

ymd -= months{2};
assert((ymd == year_month_day{year{2020}, month{3}, day{31}}));
assert(ymd.ok());
}

{ // Test year wrapping
year_month_day ymd{year{2020}, month{4}, day{31}};

ymd += months{12};
assert((ymd == year_month_day{year{2021}, month{4}, day{31}}));

ymd -= months{12};
assert((ymd == year_month_day{year{2020}, month{4}, day{31}}));
}

return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,32 +39,43 @@ using year_month_day = std::chrono::year_month_day;
constexpr bool test() {
{ // year_month_day + months
year_month_day ym{year{1234}, std::chrono::January, day{12}};
for (int i = 0; i <= 10; ++i) // TODO test wrap-around
{
year_month_day ym1 = ym + months{i};
year_month_day ym2 = months{i} + ym;
assert(static_cast<int>(ym1.year()) == 1234);
assert(static_cast<int>(ym2.year()) == 1234);
assert(ym1.month() == month(1 + i));
assert(ym2.month() == month(1 + i));
assert(ym1.day() == day{12});
assert(ym2.day() == day{12});
assert(ym1 == ym2);
for (int i = 0; i <= 10; ++i) {
year_month_day ymd1 = ym + months{i};
year_month_day ymd2 = months{i} + ym;
assert(static_cast<int>(ymd1.year()) == 1234);
assert(static_cast<int>(ymd2.year()) == 1234);
assert(ymd1.month() == month(1 + i));
assert(ymd2.month() == month(1 + i));
assert(ymd1.day() == day{12});
assert(ymd2.day() == day{12});
assert(ymd1 == ymd2);
}
// Test the year wraps around.
for (int i = 12; i <= 15; ++i) {
year_month_day ymd1 = ym + months{i};
year_month_day ymd2 = months{i} + ym;
assert(static_cast<int>(ymd1.year()) == 1235);
assert(static_cast<int>(ymd2.year()) == 1235);
assert(ymd1.month() == month(1 + i - 12));
assert(ymd2.month() == month(1 + i - 12));
assert(ymd1.day() == day{12});
assert(ymd2.day() == day{12});
assert(ymd1 == ymd2);
}
}

{ // year_month_day + years
year_month_day ym{year{1234}, std::chrono::January, day{12}};
for (int i = 0; i <= 10; ++i) {
year_month_day ym1 = ym + years{i};
year_month_day ym2 = years{i} + ym;
assert(static_cast<int>(ym1.year()) == i + 1234);
assert(static_cast<int>(ym2.year()) == i + 1234);
assert(ym1.month() == std::chrono::January);
assert(ym2.month() == std::chrono::January);
assert(ym1.day() == day{12});
assert(ym2.day() == day{12});
assert(ym1 == ym2);
year_month_day ymd1 = ym + years{i};
year_month_day ymd2 = years{i} + ym;
assert(static_cast<int>(ymd1.year()) == i + 1234);
assert(static_cast<int>(ymd2.year()) == i + 1234);
assert(ymd1.month() == std::chrono::January);
assert(ymd2.month() == std::chrono::January);
assert(ymd1.day() == day{12});
assert(ymd2.day() == day{12});
assert(ymd1 == ymd2);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,25 @@ constexpr bool test() {
for (unsigned i = 0; i <= 10; ++i) {
year y{1234};
month_day_last mdl{month{i}};
year_month_day_last ym(y, mdl);
assert(static_cast<unsigned>((ym += months{2}).month()) == i + 2);
assert(ym.year() == y);
assert(static_cast<unsigned>((ym).month()) == i + 2);
assert(ym.year() == y);
assert(static_cast<unsigned>((ym -= months{1}).month()) == i + 1);
assert(ym.year() == y);
assert(static_cast<unsigned>((ym).month()) == i + 1);
assert(ym.year() == y);
year_month_day_last ymdl(y, mdl);
assert(static_cast<unsigned>((ymdl += months{2}).month()) == i + 2);
assert(ymdl.year() == y);
assert(static_cast<unsigned>((ymdl).month()) == i + 2);
assert(ymdl.year() == y);
assert(static_cast<unsigned>((ymdl -= months{1}).month()) == i + 1);
assert(ymdl.year() == y);
assert(static_cast<unsigned>((ymdl).month()) == i + 1);
assert(ymdl.year() == y);
}

{ // Test year wrapping
year_month_day_last ymdl{year{2020}, month_day_last{month{4}}};

ymdl += months{12};
assert((ymdl == year_month_day_last{year{2021}, month_day_last{month{4}}}));

ymdl -= months{12};
assert((ymdl == year_month_day_last{year{2020}, month_day_last{month{4}}}));
}

return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,22 +38,27 @@ constexpr bool test() {

{ // year_month_day_last - years

year_month_day_last ym{year{1234}, month_day_last{December}};
year_month_day_last ymdl{year{1234}, month_day_last{December}};
for (int i = 0; i <= 10; ++i) {
year_month_day_last ym1 = ym - years{i};
assert(static_cast<int>(ym1.year()) == 1234 - i);
assert(ym1.month() == December);
year_month_day_last ymdl1 = ymdl - years{i};
assert(static_cast<int>(ymdl1.year()) == 1234 - i);
assert(ymdl1.month() == December);
}
}

{ // year_month_day_last - months

// TODO test wrapping
year_month_day_last ym{year{1234}, month_day_last{December}};
year_month_day_last ymdl{year{1234}, month_day_last{December}};
for (unsigned i = 0; i <= 10; ++i) {
year_month_day_last ym1 = ym - months{i};
assert(static_cast<int>(ym1.year()) == 1234);
assert(static_cast<unsigned>(ym1.month()) == 12U - i);
year_month_day_last ymdl1 = ymdl - months{i};
assert(static_cast<int>(ymdl1.year()) == 1234);
assert(static_cast<unsigned>(ymdl1.month()) == 12U - i);
}
// Test the year wraps around.
for (unsigned i = 12; i <= 15; ++i) {
year_month_day_last ymdl1 = ymdl - months{i};
assert(static_cast<int>(ymdl1.year()) == 1233);
assert(static_cast<unsigned>(ymdl1.month()) == 12U - i + 12);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,29 +49,37 @@ constexpr bool test() {

{ // year_month_day_last + months
year_month_day_last ym{year{1234}, month_day_last{January}};
for (int i = 0; i <= 10; ++i) // TODO test wrap-around
{
year_month_day_last ym1 = ym + months{i};
year_month_day_last ym2 = months{i} + ym;
assert(static_cast<int>(ym1.year()) == 1234);
assert(static_cast<int>(ym2.year()) == 1234);
assert(ym1.month() == month(1 + i));
assert(ym2.month() == month(1 + i));
assert(ym1 == ym2);
for (int i = 0; i <= 10; ++i) {
year_month_day_last ymdl1 = ym + months{i};
year_month_day_last ymdl2 = months{i} + ym;
assert(static_cast<int>(ymdl1.year()) == 1234);
assert(static_cast<int>(ymdl2.year()) == 1234);
assert(ymdl1.month() == month(1 + i));
assert(ymdl2.month() == month(1 + i));
assert(ymdl1 == ymdl2);
}
// Test the year wraps around.
for (int i = 12; i <= 15; ++i) {
year_month_day_last ymdl1 = ym + months{i};
year_month_day_last ymdl2 = months{i} + ym;
assert(static_cast<int>(ymdl1.year()) == 1235);
assert(static_cast<int>(ymdl2.year()) == 1235);
assert(ymdl1.month() == month(1 + i - 12));
assert(ymdl2.month() == month(1 + i - 12));
assert(ymdl1 == ymdl2);
}
}

{ // year_month_day_last + years

year_month_day_last ym{year{1234}, month_day_last{January}};
for (int i = 0; i <= 10; ++i) {
year_month_day_last ym1 = ym + years{i};
year_month_day_last ym2 = years{i} + ym;
assert(static_cast<int>(ym1.year()) == i + 1234);
assert(static_cast<int>(ym2.year()) == i + 1234);
assert(ym1.month() == std::chrono::January);
assert(ym2.month() == std::chrono::January);
assert(ym1 == ym2);
year_month_day_last ymdl1 = ym + years{i};
year_month_day_last ymdl2 = years{i} + ym;
assert(static_cast<int>(ymdl1.year()) == i + 1234);
assert(static_cast<int>(ymdl2.year()) == i + 1234);
assert(ymdl1.month() == std::chrono::January);
assert(ymdl2.month() == std::chrono::January);
assert(ymdl1 == ymdl2);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,16 @@ constexpr bool test() {
assert(ymwd.index() == 2);
}

{ // Test year wrapping
year_month_weekday ymwd{year{2020}, month{4}, weekday_indexed{Tuesday, 2}};

ymwd += months{12};
assert((ymwd == year_month_weekday{year{2021}, month{4}, weekday_indexed{Tuesday, 2}}));

ymwd -= months{12};
assert((ymwd == year_month_weekday{year{2020}, month{4}, weekday_indexed{Tuesday, 2}}));
}

return true;
}

Expand Down
Loading

0 comments on commit 3d39d2a

Please sign in to comment.