Skip to content

fix: clamp day-of-month when adding years/months (calendar overflow) - #41

Open
spokodev wants to merge 1 commit into
tolu:masterfrom
spokodev:fix/calendar-month-overflow
Open

fix: clamp day-of-month when adding years/months (calendar overflow)#41
spokodev wants to merge 1 commit into
tolu:masterfrom
spokodev:fix/calendar-month-overflow

Conversation

@spokodev

@spokodev spokodev commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

toSeconds/end overflow the calendar when the start day does not exist in the target month.

import { parse, toSeconds } from "iso8601-duration";

// Jan 31 2021 + P1M
toSeconds(parse("P1M"), new Date(2021, 0, 31)) / 86400; // 31  -> Mar 3
// expected 28 -> Feb 28

end adds years/months with the raw Date setters:

then.setMonth(then.getMonth() + duration.months);

Setting the month on Jan 31 rolls over to Mar 3 instead of clamping to Feb 28. The same happens for years (Feb 29 + P1Y becomes Mar 1 instead of Feb 28).

The @js-temporal/polyfill that the suite validates against constrains the day to the target month:

Temporal.PlainDate.from("2021-01-31").add({ months: 1 }).toString(); // 2021-02-28

Fix: add years/months on the 1st of the month, then clamp the day to the number of days in the resulting month before adding the day component. Multi-unit and leap-year cases already handled by Date (e.g. P2M from Jan 31 stays Mar 31) are unchanged.

Adds regression tests for the month, year, and month-then-day cases.

end() (and thus toSeconds()) added months/years with the raw JS Date
setters, so a day that does not exist in the target month overflowed
into the next one: e.g. from Jan 31, P1M produced Mar 3 instead of
Feb 28. This diverges from the @js-temporal/polyfill oracle the test
suite validates against, which constrains the day to the target month.

Add years/months on the 1st of the month, then clamp the day to the
number of days in the resulting month before adding the day component.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant