Skip to content

Commit

Permalink
Normative: Correct PlainYearMonth arithmetic for non-ISO calendars
Browse files Browse the repository at this point in the history
Previously, the algorithm would mix a day in calendar space with a month
and year in ISO space.

An example of where this would go wrong immediately would be subtracting
a duration from a PlainYearMonth from a calendar where the month occurred
in ISO February but had more days in it than ISO February.

For example,

    calendar = Temporal.Calendar.from('chinese');
    m = Temporal.PlainYearMonth.from({ year: 2021, month: 1, calendar })
      // (internally results in ISO reference date 2021-02-12)
    m.daysInMonth  // => 29
    m.subtract({ months: 1 })

This would previously try to call CreateTemporalDate(2021, 2, 29) and fail
due to mixing the day 29 in Chinese calendar space with year and month in
ISO calendar space.

However, in most cases this wouldn't even throw, but just produce wrong
results.

This was already handled correctly in the polyfill, but should have a
test262 test written for it.
  • Loading branch information
ptomato committed Mar 30, 2022
1 parent 8c73780 commit 61e8dd0
Showing 1 changed file with 6 additions and 2 deletions.
8 changes: 6 additions & 2 deletions spec/plainyearmonth.html
Original file line number Diff line number Diff line change
Expand Up @@ -256,13 +256,15 @@ <h1>Temporal.PlainYearMonth.prototype.add ( _temporalDurationLike_ [ , _options_
1. Set _options_ to ? GetOptionsObject(_options_).
1. Let _calendar_ be _yearMonth_.[[Calendar]].
1. Let _fieldNames_ be ? CalendarFields(_calendar_, « *"monthCode"*, *"year"* »).
1. Let _fields_ be ? PrepareTemporalFields(_yearMonth_, _fieldNames_, «»).
1. Let _sign_ be ! DurationSign(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _balanceResult_.[[Days]], 0, 0, 0, 0, 0, 0).
1. If _sign_ &lt; 0, then
1. Let _dayFromCalendar_ be ? CalendarDaysInMonth(_calendar_, _yearMonth_).
1. Let _day_ be ? ToPositiveInteger(_dayFromCalendar_).
1. Else,
1. Let _day_ be 1.
1. Let _date_ be ? CreateTemporalDate(_yearMonth_.[[ISOYear]], _yearMonth_.[[ISOMonth]], _day_, _calendar_).
1. Perform ! CreateDataPropertyOrThrow(_fields_, *"day"*, _day_).
1. Let _date_ be ? DateFromFields(_calendar_, _fields_, *undefined*).
1. Let _durationToAdd_ be ! CreateTemporalDuration(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _balanceResult_.[[Days]], 0, 0, 0, 0, 0, 0).
1. Let _optionsCopy_ be OrdinaryObjectCreate(%Object.prototype%).
1. Let _entries_ be ? EnumerableOwnPropertyNames(_options_, ~key+value~).
Expand All @@ -289,13 +291,15 @@ <h1>Temporal.PlainYearMonth.prototype.subtract ( _temporalDurationLike_ [ , _opt
1. Set _options_ to ? GetOptionsObject(_options_).
1. Let _calendar_ be _yearMonth_.[[Calendar]].
1. Let _fieldNames_ be ? CalendarFields(_calendar_, « *"monthCode"*, *"year"* »).
1. Let _fields_ be ? PrepareTemporalFields(_yearMonth_, _fieldNames_, «»).
1. Let _sign_ be ! DurationSign(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _balanceResult_.[[Days]], 0, 0, 0, 0, 0, 0).
1. If _sign_ &lt; 0, then
1. Let _dayFromCalendar_ be ? CalendarDaysInMonth(_calendar_, _yearMonth_).
1. Let _day_ be ? ToPositiveInteger(_dayFromCalendar_).
1. Else,
1. Let _day_ be 1.
1. Let _date_ be ? CreateTemporalDate(_yearMonth_.[[ISOYear]], _yearMonth_.[[ISOMonth]], _day_, _calendar_).
1. Perform ! CreateDataPropertyOrThrow(_fields_, *"day"*, _day_).
1. Let _date_ be ? DateFromFields(_calendar_, _fields_, *undefined*).
1. Let _durationToAdd_ be ! CreateTemporalDuration(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _balanceResult_.[[Days]], 0, 0, 0, 0, 0, 0).
1. Let _optionsCopy_ be OrdinaryObjectCreate(%Object.prototype%).
1. Let _entries_ be ? EnumerableOwnPropertyNames(_options_, ~key+value~).
Expand Down

0 comments on commit 61e8dd0

Please sign in to comment.