blob: 356c59a60d624e3803efcfa42774340669d9d46f [file] [log] [blame]
// Copyright (C) 2022 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-temporal.plainyearmonth.prototype.add
description: >
Calls calendar's dateFromFields method to obtain a start date for the
operation, based on the sign of the duration
info: |
8. Let _fields_ be ? PrepareTemporalFields(_yearMonth_, _fieldNames_, «»).
9. Let _sign_ be ! DurationSign(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _balanceResult_.[[Days]], 0, 0, 0, 0, 0, 0).
10. If _sign_ < 0, then
a. Let _dayFromCalendar_ be ? CalendarDaysInMonth(_calendar_, _yearMonth_).
b. Let _day_ be ? ToPositiveInteger(_dayFromCalendar_).
11. Else,
a. Let _day_ be 1.
12. Perform ! CreateDataPropertyOrThrow(_fields_, *"day"*, _day_).
13. Let _date_ be ? DateFromFields(_calendar_, _fields_, *undefined*).
includes: [deepEqual.js, temporalHelpers.js]
features: [Temporal]
---*/
class CustomCalendar extends Temporal.Calendar {
constructor() {
super("iso8601");
this.dateFromFieldsCalls = [];
}
year(date) {
// years in this calendar start and end on the same day as ISO 8601 years
return date.getISOFields().isoYear;
}
month(date) {
// this calendar has 10 months of 36 days each, plus an 11th month of 5 or 6
const { isoYear, isoMonth, isoDay } = date.getISOFields();
const isoDate = new Temporal.PlainDate(isoYear, isoMonth, isoDay);
return Math.floor((isoDate.dayOfYear - 1) / 36) + 1;
}
monthCode(date) {
return "M" + this.month(date).toString().padStart(2, "0");
}
day(date) {
return (date.dayOfYear - 1) % 36 + 1;
}
daysInMonth(date) {
if (this.month(date) < 11) return 36;
return this.daysInYear(date) - 360;
}
_dateFromFieldsImpl({ year, month, monthCode, day }) {
if (year === undefined) throw new TypeError("year required");
if (month === undefined && monthCode === undefined) throw new TypeError("one of month or monthCode required");
if (month !== undefined && month < 1) throw new RangeError("month < 1");
if (day === undefined) throw new TypeError("day required");
if (monthCode !== undefined) {
const numberPart = +(monthCode.slice(1));
if ("M" + `${numberPart}`.padStart(2, "0") !== monthCode) throw new RangeError("invalid monthCode");
if (month === undefined) {
month = numberPart;
} else if (month !== numberPart) {
throw new RangeError("month and monthCode must match");
}
}
const isoDayOfYear = (month - 1) * 36 + day;
return new Temporal.PlainDate(year, 1, 1).add({ days: isoDayOfYear - 1 }).withCalendar(this);
}
dateFromFields(...args) {
this.dateFromFieldsCalls.push(args);
return this._dateFromFieldsImpl(...args);
}
yearMonthFromFields(fields, options) {
const { isoYear, isoMonth, isoDay } = this._dateFromFieldsImpl({ ...fields, day: 1 }, options).getISOFields();
return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
}
monthDayFromFields(fields, options) {
const { isoYear, isoMonth, isoDay } = this._dateFromFieldsImpl({ ...fields, year: 2000 }, options).getISOFields();
return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
}
dateAdd(date, duration, options) {
if (duration.months) throw new Error("adding months not implemented in this test");
return super.dateAdd(date, duration, options);
}
toString() {
return "thirty-six";
}
}
const calendar = new CustomCalendar();
const month2 = Temporal.PlainYearMonth.from({ year: 2022, month: 2, calendar });
const lessThanOneMonth = new Temporal.Duration(0, 0, 0, 35);
const oneMonth = new Temporal.Duration(0, 0, 0, 36);
// Reference ISO dates in the custom calendar:
// M01 = 01-01
// M02 = 02-06
// M03 = 03-14
calendar.dateFromFieldsCalls = [];
TemporalHelpers.assertPlainYearMonth(
month2.add(lessThanOneMonth),
2022, 2, "M02",
"adding positive less than one month's worth of days yields the same month",
/* era = */ undefined, /* eraYear = */ undefined, /* referenceISODay = */ 6
);
assert.sameValue(calendar.dateFromFieldsCalls.length, 1, "dateFromFields was called");
assert.deepEqual(
calendar.dateFromFieldsCalls[0][0],
{ year: 2022, monthCode: "M02", day: 1 },
"first day of month 2 passed to dateFromFields when adding positive duration"
);
assert.sameValue(calendar.dateFromFieldsCalls[0][1], undefined, "undefined options passed");
calendar.dateFromFieldsCalls = [];
TemporalHelpers.assertPlainYearMonth(
month2.add(oneMonth),
2022, 3, "M03",
"adding positive one month's worth of days yields the following month",
/* era = */ undefined, /* eraYear = */ undefined, /* referenceISODay = */ 14
);
assert.sameValue(calendar.dateFromFieldsCalls.length, 1, "dateFromFields was called");
assert.deepEqual(
calendar.dateFromFieldsCalls[0][0],
{ year: 2022, monthCode: "M02", day: 1 },
"first day of month 2 passed to dateFromFields when adding positive duration"
);
assert.sameValue(calendar.dateFromFieldsCalls[0][1], undefined, "undefined options passed");
calendar.dateFromFieldsCalls = [];
TemporalHelpers.assertPlainYearMonth(
month2.add(lessThanOneMonth.negated()),
2022, 2, "M02",
"adding negative less than one month's worth of days yields the same month",
/* era = */ undefined, /* eraYear = */ undefined, /* referenceISODay = */ 6
);
assert.sameValue(calendar.dateFromFieldsCalls.length, 1, "dateFromFields was called");
assert.deepEqual(
calendar.dateFromFieldsCalls[0][0],
{ year: 2022, monthCode: "M02", day: 36 },
"last day of month 2 passed to dateFromFields when adding negative duration"
);
assert.sameValue(calendar.dateFromFieldsCalls[0][1], undefined, "undefined options passed");
calendar.dateFromFieldsCalls = [];
TemporalHelpers.assertPlainYearMonth(
month2.add(oneMonth.negated()),
2022, 1, "M01",
"adding negative one month's worth of days yields the previous month",
/* era = */ undefined, /* eraYear = */ undefined, /* referenceISODay = */ 1
);
assert.sameValue(calendar.dateFromFieldsCalls.length, 1, "dateFromFields was called");
assert.deepEqual(
calendar.dateFromFieldsCalls[0][0],
{ year: 2022, monthCode: "M02", day: 36 },
"last day of month 2 passed to dateFromFields when adding negative duration"
);
assert.sameValue(calendar.dateFromFieldsCalls[0][1], undefined, "undefined options passed");