Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 40 additions & 9 deletions polyfill/lib/ecmascript.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1715,6 +1715,25 @@ export function CalendarDateAdd(calendar, isoDate, dateDuration, overflow) {
return result;
}

function CalendarDateLastDayOfMonth(calendar, isoDate) {
if (calendar === 'iso8601') {
let maxDay = ISODaysInMonth(isoDate.year, isoDate.month);
return { year: isoDate.year, month: isoDate.month, day: maxDay };
}
let previousIsoDate = isoDate;
let nextIsoDate = isoDate;
let calendarDate = calendarImplForID(calendar).isoToDate(nextIsoDate, { year: true, month: true, day: true });
let initialMonth = calendarDate.month;
while (calendarDate.month === initialMonth) {
previousIsoDate = nextIsoDate;
// If the date overflows the maxium range, an exception will be thrown,
// which is the desired behavior
nextIsoDate = CalendarDateAdd(calendar, nextIsoDate, { days: 1 }, 'constrain');
calendarDate = calendarImplForID(calendar).isoToDate(nextIsoDate, { year: true, month: true, day: true });
}
return previousIsoDate;
}

export function CalendarDateUntil(calendar, isoDate, isoOtherDate, largestUnit) {
return calendarImplForID(calendar).dateUntil(isoDate, isoOtherDate, largestUnit);
}
Expand Down Expand Up @@ -4129,23 +4148,35 @@ export function AddDurationToTime(operation, temporalTime, durationLike) {
export function AddDurationToYearMonth(operation, yearMonth, durationLike, options) {
let duration = ToTemporalDuration(durationLike);
if (operation === 'subtract') duration = CreateNegatedTemporalDuration(duration);
const sign = DurationSign(duration);
duration = ToDateDurationRecordWithoutTime(duration);
const resolvedOptions = GetOptionsObject(options);
const overflow = GetTemporalOverflowOption(resolvedOptions);
const sign = DurationSign(duration);

const durationYearsMonthsOnly = AdjustDateDurationRecord(duration, 0, 0);
const durationWithoutYearsMonths = {
years: 0,
months: 0,
weeks: duration.weeks,
days: duration.days
};
const calendar = GetSlot(yearMonth, CALENDAR);
const fields = ISODateToFields(calendar, GetSlot(yearMonth, ISO_DATE), 'year-month');
fields.day = 1;
let startDate = CalendarDateFromFields(calendar, fields, 'constrain');
if (sign < 0) {
const nextMonth = CalendarDateAdd(calendar, startDate, { months: 1 }, 'constrain');
startDate = BalanceISODate(nextMonth.year, nextMonth.month, nextMonth.day - 1);
}
const durationToAdd = ToDateDurationRecordWithoutTime(duration);
RejectDateRange(startDate);
const addedDate = CalendarDateAdd(calendar, startDate, durationToAdd, overflow);
const dateWithYearMonthAdded = CalendarDateAdd(calendar, startDate, durationYearsMonthsOnly, overflow);
let addedDate;
if (!(durationWithoutYearsMonths.weeks === 0 && durationWithoutYearsMonths.days === 0)) {
if (sign < 0) {
startDate = CalendarDateLastDayOfMonth(calendar, dateWithYearMonthAdded);
} else {
startDate = dateWithYearMonthAdded;
}
addedDate = CalendarDateAdd(calendar, startDate, durationWithoutYearsMonths, overflow);
} else {
addedDate = dateWithYearMonthAdded;
}
const addedDateFields = ISODateToFields(calendar, addedDate, 'year-month');

const isoDate = CalendarYearMonthFromFields(calendar, addedDateFields, overflow);
return CreateTemporalYearMonth(isoDate, calendar);
}
Expand Down
43 changes: 43 additions & 0 deletions spec/calendar.html
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,49 @@ <h1>
</emu-alg>
</emu-clause>

<emu-clause id="sec-temporal-nonisodatelastdayofmonth" type="implementation-defined abstract operation">
<h1>
NonISODateLastDayOfMonth (
_calendar_: a calendar type that is not *"iso8601"*,
_year_: an integer,
_month_: an integer between 1 and 13 (inclusive),
): an ISO Date Record
</h1>
<dl class="header">
<dt>description</dt>
<dd>
The operation performs implementation-defined processing to determine the last day of the month denoted by _year_ and _month_. It returns an ISO Date Record representing a date in the reckoning of _calendar_ that has the the same year and month as _year_ and _month_, and the day set to the last day of _month_.
</dd>
</dl>
</emu-clause>
Comment on lines +598 to +612
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be better to specify CalendarDateLastDayOfMonth in terms of existing implementation-defined operations if possible, then we wouldn't have to have another implementation-defined operation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that if we specify it in terms of NonISODateAdd, there's no way to "catch" the exception if the result of the addition is outside the representable range and we want to back off to the last representable day. (Or is there a way to catch exceptions in spec language?)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the way to do it would be to check ISODateWithinLimits after we call CalendarDateLastDayOfMonth.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I don't get it -- if CalendarDateLastDayOfMonth calls ISODateAdd, then if the result of the addition is out of range, CalendarDateLastDayOfMonth will return a throw completion record. And then there's nothing to check ISODateWithinLimits from. Am I missing something?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see what you mean. My comment doesn't make sense anymore. Well, you could do something like this:

  1. Let addCompletion be Completion(CalendarDateAdd(calendar, ...))
  2. If addCompletion is a throw completion, ...

But that's unusual and I don't much like it.


<emu-clause id="sec-temporal-calendardatelastdayofmonth" type="abstract operation">
<h1>
CalendarDateLastDayOfMonth (
_calendar_: a calendar type,
_isoDate_: an ISO Date Record,
): an ISO Date Record
</h1>
<dl class="header">
<dt>description</dt>
<dd>
It determines the last day in the month _isoMonth_ of the year _isoYear_ using the years, months
and weeks reckoning of _calendar_, and returns an ISO Date Record with the same year and month
as _isoDate_, and the day set to the last day in the month.
</dd>
</dl>
<emu-alg>
1. If _calendar_ is *"iso8601"*, then
1. If _isoDate_.[[Year]] equals 275760 and _isoDate_.[[Month]] equals 9, then
1. Let _maxDay_ be 13.
1. Else,
1. Let _maxDay_ be ISODaysInMonth(_isoDate_.[[Year]], _isoDate_.[[Month]]).
1. Return CreateISODateRecord(_isoDate_.[[Year]], _isoDate_.[[Month]], _maxDay_).
1. Else,
1. Return NonISODateLastDayOfMonth(_calendar_, _isoDate_.[[Year]], _isoDate_.[[Month]]).
</emu-alg>
</emu-clause>

<emu-clause id="sec-temporal-nonisodateuntil" type="implementation-defined abstract operation">
<h1>
NonISODateUntil (
Expand Down
23 changes: 14 additions & 9 deletions spec/plainyearmonth.html
Original file line number Diff line number Diff line change
Expand Up @@ -639,22 +639,27 @@ <h1>
<emu-alg>
1. Let _duration_ be ? ToTemporalDuration(_temporalDurationLike_).
1. If _operation_ is ~subtract~, set _duration_ to CreateNegatedTemporalDuration(_duration_).
1. Let _sign_ be DurationSign(_duration_).
1. Set _duration_ to ToDateDurationRecordWithoutTime(_duration_).
1. Let _resolvedOptions_ be ? GetOptionsObject(_options_).
1. Let _overflow_ be ? GetTemporalOverflowOption(_resolvedOptions_).
1. Let _sign_ be DurationSign(_duration_).
1. Let _durationYearsMonthsOnly_ be ! AdjustDateDurationRecord(_duration_, 0, 0).
1. Let _durationWithoutYearsMonths_ be ? CreateDateDurationRecord(0, 0, _duration_.[[Weeks]], _duration_.[[Days]]).
1. Let _calendar_ be _yearMonth_.[[Calendar]].
1. Let _fields_ be ISODateToFields(_calendar_, _yearMonth_.[[ISODate]], ~year-month~).
1. Set _fields_.[[Day]] to 1.
1. Let _intermediateDate_ be ? CalendarDateFromFields(_calendar_, _fields_, ~constrain~).
1. If _sign_ &lt; 0, then
1. Let _oneMonthDuration_ be ! CreateDateDurationRecord(0, 1, 0, 0).
1. Let _nextMonth_ be ? CalendarDateAdd(_calendar_, _intermediateDate_, _oneMonthDuration_, ~constrain~).
1. Let _date_ be BalanceISODate(_nextMonth_.[[Year]], _nextMonth_.[[Month]], _nextMonth_.[[Day]] - 1).
1. Assert: ISODateWithinLimits(_date_) is *true*.
1. If IsValidISODate(_intermediateDate_.[[Year]], _intermediateDate_.[[Month]], _intermediateDate_.[[Day]]) is *false*, throw a *RangeError* exception.
1. Let _dateWithYearMonthAdded_ be ? CalendarDateAdd(_calendar_, _intermediateDate_, _durationYearsMonthsOnly_, _overflow_).
1. Let _addedDate_ be ~unset~.
1. If _durationWithoutYearsMonths_.[[Weeks]] is not equal to 0 or _durationWithoutYearsMonths_.[[Days]] is not equal to 0, then
1. If _sign_ &lt; 0, then
1. Set _intermediateDate_ to CalendarDateLastDayOfMonth(_calendar_, _dateWithYearMonthAdded_).
1. Else,
1. Set _intermediateDate_ to _dateWithYearMonthAdded_.
1. Set _addedDate_ to ? CalendarDateAdd(_calendar_, _intermediateDate_, _durationWithoutYearsMonths_, _overflow_).
1. Else,
1. Let _date_ be _intermediateDate_.
1. Let _durationToAdd_ be ToDateDurationRecordWithoutTime(_duration_).
1. Let _addedDate_ be ? CalendarDateAdd(_calendar_, _date_, _durationToAdd_, _overflow_).
1. Set _addedDate_ to _dateWithYearMonthAdded_.
1. Let _addedDateFields_ be ISODateToFields(_calendar_, _addedDate_, ~year-month~).
1. Let _isoDate_ be ? CalendarYearMonthFromFields(_calendar_, _addedDateFields_, _overflow_).
1. Return ! CreateTemporalYearMonth(_isoDate_, _calendar_).
Expand Down
Loading