Skip to content

Commit 3d39d2a

Browse files
mordantecopybara-github
authored andcommitted
[libc++][chrono] Fixes year_month year wrapping. (#74938)
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
1 parent 2b49c8a commit 3d39d2a

File tree

15 files changed

+329
-161
lines changed

15 files changed

+329
-161
lines changed

include/__chrono/year_month.h

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ class year_month {
3636
: __y_{__yval}, __m_{__mval} {}
3737
_LIBCPP_HIDE_FROM_ABI inline constexpr chrono::year year() const noexcept { return __y_; }
3838
_LIBCPP_HIDE_FROM_ABI inline constexpr chrono::month month() const noexcept { return __m_; }
39-
_LIBCPP_HIDE_FROM_ABI inline constexpr year_month& operator+=(const months& __dm) noexcept { this->__m_ += __dm; return *this; }
40-
_LIBCPP_HIDE_FROM_ABI inline constexpr year_month& operator-=(const months& __dm) noexcept { this->__m_ -= __dm; return *this; }
41-
_LIBCPP_HIDE_FROM_ABI inline constexpr year_month& operator+=(const years& __dy) noexcept { this->__y_ += __dy; return *this; }
42-
_LIBCPP_HIDE_FROM_ABI inline constexpr year_month& operator-=(const years& __dy) noexcept { this->__y_ -= __dy; return *this; }
39+
_LIBCPP_HIDE_FROM_ABI inline constexpr year_month& operator+=(const months& __dm) noexcept;
40+
_LIBCPP_HIDE_FROM_ABI inline constexpr year_month& operator-=(const months& __dm) noexcept;
41+
_LIBCPP_HIDE_FROM_ABI inline constexpr year_month& operator+=(const years& __dy) noexcept;
42+
_LIBCPP_HIDE_FROM_ABI inline constexpr year_month& operator-=(const years& __dy) noexcept;
4343
_LIBCPP_HIDE_FROM_ABI inline constexpr bool ok() const noexcept { return __y_.ok() && __m_.ok(); }
4444
};
4545

@@ -92,6 +92,26 @@ _LIBCPP_HIDE_FROM_ABI constexpr
9292
year_month operator-(const year_month& __lhs, const years& __rhs) noexcept
9393
{ return __lhs + -__rhs; }
9494

95+
_LIBCPP_HIDE_FROM_ABI inline constexpr year_month& year_month::operator+=(const months& __dm) noexcept {
96+
*this = *this + __dm;
97+
return *this;
98+
}
99+
100+
_LIBCPP_HIDE_FROM_ABI inline constexpr year_month& year_month::operator-=(const months& __dm) noexcept {
101+
*this = *this - __dm;
102+
return *this;
103+
}
104+
105+
_LIBCPP_HIDE_FROM_ABI inline constexpr year_month& year_month::operator+=(const years& __dy) noexcept {
106+
*this = *this + __dy;
107+
return *this;
108+
}
109+
110+
_LIBCPP_HIDE_FROM_ABI inline constexpr year_month& year_month::operator-=(const years& __dy) noexcept {
111+
*this = *this - __dy;
112+
return *this;
113+
}
114+
95115
} // namespace chrono
96116

97117
_LIBCPP_END_NAMESPACE_STD

test/std/time/time.cal/time.cal.ym/time.cal.ym.members/plus_minus_equal_month.pass.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ constexpr bool test() {
3838
assert(ym.year() == y);
3939
}
4040

41+
{ // Test year wrapping
42+
year_month ym{year{2020}, month{4}};
43+
44+
ym += months{12};
45+
assert((ym == year_month{year{2021}, month{4}}));
46+
47+
ym -= months{12};
48+
assert((ym == year_month{year{2020}, month{4}}));
49+
}
50+
4151
return true;
4252
}
4353

test/std/time/time.cal/time.cal.ym/time.cal.ym.nonmembers/minus.pass.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,17 @@ constexpr bool test() {
4646
{ // year_month - months
4747

4848
year_month ym{year{1234}, std::chrono::November};
49-
for (int i = 0; i <= 10; ++i) // TODO test wrap-around
50-
{
49+
for (int i = 0; i <= 10; ++i) {
5150
year_month ym1 = ym - months{i};
5251
assert(static_cast<int>(ym1.year()) == 1234);
5352
assert(ym1.month() == month(11 - i));
5453
}
54+
// Test the year wraps around.
55+
for (int i = 12; i <= 15; ++i) {
56+
year_month ym1 = ym - months{i};
57+
assert(static_cast<int>(ym1.year()) == 1233);
58+
assert(ym1.month() == month(11 - i + 12));
59+
}
5560
}
5661

5762
{ // year_month - year_month

test/std/time/time.cal/time.cal.ym/time.cal.ym.nonmembers/plus.pass.cpp

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,19 @@
2929

3030
#include "test_macros.h"
3131

32-
using year = std::chrono::year;
33-
using years = std::chrono::years;
34-
using month = std::chrono::month;
35-
using months = std::chrono::months;
32+
using year = std::chrono::year;
33+
using years = std::chrono::years;
34+
using month = std::chrono::month;
35+
using months = std::chrono::months;
3636
using year_month = std::chrono::year_month;
3737

3838
// year_month + years
3939
constexpr bool test_ym_plus_y() {
4040
ASSERT_NOEXCEPT(std::declval<year_month>() + std::declval<years>());
4141
ASSERT_NOEXCEPT(std::declval<years>() + std::declval<year_month>());
4242

43-
ASSERT_SAME_TYPE(
44-
year_month, decltype(std::declval<year_month>() + std::declval<years>()));
45-
ASSERT_SAME_TYPE(
46-
year_month, decltype(std::declval<years>() + std::declval<year_month>()));
43+
ASSERT_SAME_TYPE(year_month, decltype(std::declval<year_month>() + std::declval<years>()));
44+
ASSERT_SAME_TYPE(year_month, decltype(std::declval<years>() + std::declval<year_month>()));
4745

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

67-
ASSERT_SAME_TYPE(year_month, decltype(std::declval<year_month>() +
68-
std::declval<months>()));
69-
ASSERT_SAME_TYPE(year_month, decltype(std::declval<months>() +
70-
std::declval<year_month>()));
65+
ASSERT_SAME_TYPE(year_month, decltype(std::declval<year_month>() + std::declval<months>()));
66+
ASSERT_SAME_TYPE(year_month, decltype(std::declval<months>() + std::declval<year_month>()));
67+
68+
{
69+
// [time.cal.ym.nonmembers]/4
70+
// Returns: A year_month value z such that z.ok() && z - ym == dm is true.
71+
year_month ym = {year{1234}, std::chrono::January};
72+
months dm = months{42};
73+
year_month z = ym + dm;
74+
assert(z.ok() && z - ym == dm);
75+
}
7176

7277
year_month ym{year{1234}, std::chrono::January};
7378
for (int i = 0; i <= 11; ++i) {
@@ -89,7 +94,6 @@ constexpr bool test_ym_plus_m() {
8994
assert(ym2.month() == month(1 + i % 12));
9095
assert(ym1 == ym2);
9196
}
92-
9397
return true;
9498
}
9599

test/std/time/time.cal/time.cal.ymd/time.cal.ymd.members/plus_minus_equal_month.pass.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,40 @@ constexpr bool test() {
4242
assert(static_cast<unsigned>((ymd).month()) == i + 1);
4343
}
4444

45+
{ // Validate the ok status when the day is not present in the new month.
46+
year_month_day ymd{year{2020}, month{3}, day{31}};
47+
ymd += months{1};
48+
assert((ymd == year_month_day{year{2020}, month{4}, day{31}}));
49+
assert(!ymd.ok());
50+
51+
ymd -= months{1};
52+
assert((ymd == year_month_day{year{2020}, month{3}, day{31}}));
53+
assert(ymd.ok());
54+
}
55+
56+
{ // Validate the ok status when the day becomes present in the new month.
57+
year_month_day ymd{year{2020}, month{4}, day{31}};
58+
assert(!ymd.ok());
59+
60+
ymd += months{1};
61+
assert((ymd == year_month_day{year{2020}, month{5}, day{31}}));
62+
assert(ymd.ok());
63+
64+
ymd -= months{2};
65+
assert((ymd == year_month_day{year{2020}, month{3}, day{31}}));
66+
assert(ymd.ok());
67+
}
68+
69+
{ // Test year wrapping
70+
year_month_day ymd{year{2020}, month{4}, day{31}};
71+
72+
ymd += months{12};
73+
assert((ymd == year_month_day{year{2021}, month{4}, day{31}}));
74+
75+
ymd -= months{12};
76+
assert((ymd == year_month_day{year{2020}, month{4}, day{31}}));
77+
}
78+
4579
return true;
4680
}
4781

test/std/time/time.cal/time.cal.ymd/time.cal.ymd.nonmembers/plus.pass.cpp

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,32 +39,43 @@ using year_month_day = std::chrono::year_month_day;
3939
constexpr bool test() {
4040
{ // year_month_day + months
4141
year_month_day ym{year{1234}, std::chrono::January, day{12}};
42-
for (int i = 0; i <= 10; ++i) // TODO test wrap-around
43-
{
44-
year_month_day ym1 = ym + months{i};
45-
year_month_day ym2 = months{i} + ym;
46-
assert(static_cast<int>(ym1.year()) == 1234);
47-
assert(static_cast<int>(ym2.year()) == 1234);
48-
assert(ym1.month() == month(1 + i));
49-
assert(ym2.month() == month(1 + i));
50-
assert(ym1.day() == day{12});
51-
assert(ym2.day() == day{12});
52-
assert(ym1 == ym2);
42+
for (int i = 0; i <= 10; ++i) {
43+
year_month_day ymd1 = ym + months{i};
44+
year_month_day ymd2 = months{i} + ym;
45+
assert(static_cast<int>(ymd1.year()) == 1234);
46+
assert(static_cast<int>(ymd2.year()) == 1234);
47+
assert(ymd1.month() == month(1 + i));
48+
assert(ymd2.month() == month(1 + i));
49+
assert(ymd1.day() == day{12});
50+
assert(ymd2.day() == day{12});
51+
assert(ymd1 == ymd2);
52+
}
53+
// Test the year wraps around.
54+
for (int i = 12; i <= 15; ++i) {
55+
year_month_day ymd1 = ym + months{i};
56+
year_month_day ymd2 = months{i} + ym;
57+
assert(static_cast<int>(ymd1.year()) == 1235);
58+
assert(static_cast<int>(ymd2.year()) == 1235);
59+
assert(ymd1.month() == month(1 + i - 12));
60+
assert(ymd2.month() == month(1 + i - 12));
61+
assert(ymd1.day() == day{12});
62+
assert(ymd2.day() == day{12});
63+
assert(ymd1 == ymd2);
5364
}
5465
}
5566

5667
{ // year_month_day + years
5768
year_month_day ym{year{1234}, std::chrono::January, day{12}};
5869
for (int i = 0; i <= 10; ++i) {
59-
year_month_day ym1 = ym + years{i};
60-
year_month_day ym2 = years{i} + ym;
61-
assert(static_cast<int>(ym1.year()) == i + 1234);
62-
assert(static_cast<int>(ym2.year()) == i + 1234);
63-
assert(ym1.month() == std::chrono::January);
64-
assert(ym2.month() == std::chrono::January);
65-
assert(ym1.day() == day{12});
66-
assert(ym2.day() == day{12});
67-
assert(ym1 == ym2);
70+
year_month_day ymd1 = ym + years{i};
71+
year_month_day ymd2 = years{i} + ym;
72+
assert(static_cast<int>(ymd1.year()) == i + 1234);
73+
assert(static_cast<int>(ymd2.year()) == i + 1234);
74+
assert(ymd1.month() == std::chrono::January);
75+
assert(ymd2.month() == std::chrono::January);
76+
assert(ymd1.day() == day{12});
77+
assert(ymd2.day() == day{12});
78+
assert(ymd1 == ymd2);
6879
}
6980
}
7081

test/std/time/time.cal/time.cal.ymdlast/time.cal.ymdlast.members/plus_minus_equal_month.pass.cpp

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,25 @@ constexpr bool test() {
2929
for (unsigned i = 0; i <= 10; ++i) {
3030
year y{1234};
3131
month_day_last mdl{month{i}};
32-
year_month_day_last ym(y, mdl);
33-
assert(static_cast<unsigned>((ym += months{2}).month()) == i + 2);
34-
assert(ym.year() == y);
35-
assert(static_cast<unsigned>((ym).month()) == i + 2);
36-
assert(ym.year() == y);
37-
assert(static_cast<unsigned>((ym -= months{1}).month()) == i + 1);
38-
assert(ym.year() == y);
39-
assert(static_cast<unsigned>((ym).month()) == i + 1);
40-
assert(ym.year() == y);
32+
year_month_day_last ymdl(y, mdl);
33+
assert(static_cast<unsigned>((ymdl += months{2}).month()) == i + 2);
34+
assert(ymdl.year() == y);
35+
assert(static_cast<unsigned>((ymdl).month()) == i + 2);
36+
assert(ymdl.year() == y);
37+
assert(static_cast<unsigned>((ymdl -= months{1}).month()) == i + 1);
38+
assert(ymdl.year() == y);
39+
assert(static_cast<unsigned>((ymdl).month()) == i + 1);
40+
assert(ymdl.year() == y);
41+
}
42+
43+
{ // Test year wrapping
44+
year_month_day_last ymdl{year{2020}, month_day_last{month{4}}};
45+
46+
ymdl += months{12};
47+
assert((ymdl == year_month_day_last{year{2021}, month_day_last{month{4}}}));
48+
49+
ymdl -= months{12};
50+
assert((ymdl == year_month_day_last{year{2020}, month_day_last{month{4}}}));
4151
}
4252

4353
return true;

test/std/time/time.cal/time.cal.ymdlast/time.cal.ymdlast.nonmembers/minus.pass.cpp

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,22 +38,27 @@ constexpr bool test() {
3838

3939
{ // year_month_day_last - years
4040

41-
year_month_day_last ym{year{1234}, month_day_last{December}};
41+
year_month_day_last ymdl{year{1234}, month_day_last{December}};
4242
for (int i = 0; i <= 10; ++i) {
43-
year_month_day_last ym1 = ym - years{i};
44-
assert(static_cast<int>(ym1.year()) == 1234 - i);
45-
assert(ym1.month() == December);
43+
year_month_day_last ymdl1 = ymdl - years{i};
44+
assert(static_cast<int>(ymdl1.year()) == 1234 - i);
45+
assert(ymdl1.month() == December);
4646
}
4747
}
4848

4949
{ // year_month_day_last - months
5050

51-
// TODO test wrapping
52-
year_month_day_last ym{year{1234}, month_day_last{December}};
51+
year_month_day_last ymdl{year{1234}, month_day_last{December}};
5352
for (unsigned i = 0; i <= 10; ++i) {
54-
year_month_day_last ym1 = ym - months{i};
55-
assert(static_cast<int>(ym1.year()) == 1234);
56-
assert(static_cast<unsigned>(ym1.month()) == 12U - i);
53+
year_month_day_last ymdl1 = ymdl - months{i};
54+
assert(static_cast<int>(ymdl1.year()) == 1234);
55+
assert(static_cast<unsigned>(ymdl1.month()) == 12U - i);
56+
}
57+
// Test the year wraps around.
58+
for (unsigned i = 12; i <= 15; ++i) {
59+
year_month_day_last ymdl1 = ymdl - months{i};
60+
assert(static_cast<int>(ymdl1.year()) == 1233);
61+
assert(static_cast<unsigned>(ymdl1.month()) == 12U - i + 12);
5762
}
5863
}
5964

test/std/time/time.cal/time.cal.ymdlast/time.cal.ymdlast.nonmembers/plus.pass.cpp

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -49,29 +49,37 @@ constexpr bool test() {
4949

5050
{ // year_month_day_last + months
5151
year_month_day_last ym{year{1234}, month_day_last{January}};
52-
for (int i = 0; i <= 10; ++i) // TODO test wrap-around
53-
{
54-
year_month_day_last ym1 = ym + months{i};
55-
year_month_day_last ym2 = months{i} + ym;
56-
assert(static_cast<int>(ym1.year()) == 1234);
57-
assert(static_cast<int>(ym2.year()) == 1234);
58-
assert(ym1.month() == month(1 + i));
59-
assert(ym2.month() == month(1 + i));
60-
assert(ym1 == ym2);
52+
for (int i = 0; i <= 10; ++i) {
53+
year_month_day_last ymdl1 = ym + months{i};
54+
year_month_day_last ymdl2 = months{i} + ym;
55+
assert(static_cast<int>(ymdl1.year()) == 1234);
56+
assert(static_cast<int>(ymdl2.year()) == 1234);
57+
assert(ymdl1.month() == month(1 + i));
58+
assert(ymdl2.month() == month(1 + i));
59+
assert(ymdl1 == ymdl2);
60+
}
61+
// Test the year wraps around.
62+
for (int i = 12; i <= 15; ++i) {
63+
year_month_day_last ymdl1 = ym + months{i};
64+
year_month_day_last ymdl2 = months{i} + ym;
65+
assert(static_cast<int>(ymdl1.year()) == 1235);
66+
assert(static_cast<int>(ymdl2.year()) == 1235);
67+
assert(ymdl1.month() == month(1 + i - 12));
68+
assert(ymdl2.month() == month(1 + i - 12));
69+
assert(ymdl1 == ymdl2);
6170
}
6271
}
6372

6473
{ // year_month_day_last + years
65-
6674
year_month_day_last ym{year{1234}, month_day_last{January}};
6775
for (int i = 0; i <= 10; ++i) {
68-
year_month_day_last ym1 = ym + years{i};
69-
year_month_day_last ym2 = years{i} + ym;
70-
assert(static_cast<int>(ym1.year()) == i + 1234);
71-
assert(static_cast<int>(ym2.year()) == i + 1234);
72-
assert(ym1.month() == std::chrono::January);
73-
assert(ym2.month() == std::chrono::January);
74-
assert(ym1 == ym2);
76+
year_month_day_last ymdl1 = ym + years{i};
77+
year_month_day_last ymdl2 = years{i} + ym;
78+
assert(static_cast<int>(ymdl1.year()) == i + 1234);
79+
assert(static_cast<int>(ymdl2.year()) == i + 1234);
80+
assert(ymdl1.month() == std::chrono::January);
81+
assert(ymdl2.month() == std::chrono::January);
82+
assert(ymdl1 == ymdl2);
7583
}
7684
}
7785

test/std/time/time.cal/time.cal.ymwd/time.cal.ymwd.members/plus_minus_equal_month.pass.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,16 @@ constexpr bool test() {
5454
assert(ymwd.index() == 2);
5555
}
5656

57+
{ // Test year wrapping
58+
year_month_weekday ymwd{year{2020}, month{4}, weekday_indexed{Tuesday, 2}};
59+
60+
ymwd += months{12};
61+
assert((ymwd == year_month_weekday{year{2021}, month{4}, weekday_indexed{Tuesday, 2}}));
62+
63+
ymwd -= months{12};
64+
assert((ymwd == year_month_weekday{year{2020}, month{4}, weekday_indexed{Tuesday, 2}}));
65+
}
66+
5767
return true;
5868
}
5969

0 commit comments

Comments
 (0)