Skip to content

Commit

Permalink
RefISOYear and RefISODay internal slots
Browse files Browse the repository at this point in the history
Temporal.YearMonth gets a RefISODay internal slot and Temporal.MonthDay
gets a RefISOYear internal slot, as discussed in #391. This will be
required for calendar support.
  • Loading branch information
ptomato committed May 28, 2020
1 parent 3e6f496 commit b6347fd
Show file tree
Hide file tree
Showing 15 changed files with 107 additions and 54 deletions.
12 changes: 7 additions & 5 deletions docs/monthday.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,22 @@ A `Temporal.MonthDay` can be converted into a `Temporal.Date` by combining it wi

## Constructor

### **new Temporal.MonthDay**(_isoMonth_: number, _isoDay_: number) : Temporal.MonthDay
### **new Temporal.MonthDay**(_isoMonth_: number, _isoDay_: number, _refISOYear_: number = 1972) : Temporal.MonthDay

**Parameters:**
- `isoMonth` (number): A month, ranging between 1 and 12 inclusive.
- `isoDay` (number): A day of the month, ranging between 1 and 31 inclusive.
- `refISOYear` (optional number): A reference year, used for disambiguation when implementing other calendar systems.
You can omit this parameter unless using a non-ISO-8601 calendar.

**Returns:** a new `Temporal.MonthDay` object.

Use this constructor if you have the correct parameters for the date already as individual number values.
Use this constructor if you have the correct parameters for the date already as individual number values, or you are implementing a custom calendar.
Otherwise, `Temporal.MonthDay.from()`, which accepts more kinds of input and allows disambiguation behaviour, is probably more convenient.

All values are given as reckoned in the [ISO 8601 calendar](https://en.wikipedia.org/wiki/ISO_8601#Dates).
Together, `isoMonth` and `isoDay` must represent a valid date in at least one year of that calendar.
For example, February 29 (Leap day in the ISO 8601 calendar) is a valid value for `Temporal.MonthDay`, even though that date does not occur every year.
Together, `refISOYear`, `isoMonth` and `isoDay` must represent a valid date in that calendar.
For example, February 29 (Leap day in the ISO 8601 calendar) is a valid value for `Temporal.MonthDay`, even though that date does not occur every year, because the default value of `refISOYear` is 1972 which is a leap year.

> **NOTE**: The `isoMonth` argument ranges from 1 to 12, which is different from legacy `Date` where months are represented by zero-based indices (0 to 11).
Expand Down Expand Up @@ -280,7 +282,7 @@ Object.assign({}, md).day // => undefined
Object.assign({}, md.getFields()).day // => 24
```

### monthDay.**getISOCalendarFields**(): { month: number, day: number }
### monthDay.**getISOCalendarFields**(): { month: number, day: number, refISOYear: number }

**Returns:** a plain object with properties expressing `monthDay` in the ISO 8601 calendar.

Expand Down
10 changes: 6 additions & 4 deletions docs/yearmonth.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,21 @@ A `Temporal.YearMonth` can be converted into a `Temporal.Date` by combining it w

## Constructor

### **new Temporal.YearMonth**(_isoYear_: number, _isoMonth_: number) : Temporal.YearMonth
### **new Temporal.YearMonth**(_isoYear_: number, _isoMonth_: number, _refISODay_: number = 1) : Temporal.YearMonth

**Parameters:**
- `isoYear` (number): A year.
- `isoMonth` (number): A month, ranging between 1 and 12 inclusive.
- `refISODay` (optional number): A reference day, used for disambiguation when implementing other calendar systems.
You can omit this parameter unless using a non-ISO-8601 calendar.

**Returns:** a new `Temporal.YearMonth` object.

Use this constructor if you have the correct parameters already as individual number values.
Use this constructor if you have the correct parameters already as individual number values, or you are implementing a custom calendar.
Otherwise, `Temporal.YearMonth.from()`, which accepts more kinds of input and allows disambiguation behaviour, is probably more convenient.

All values are given as reckoned in the [ISO 8601 calendar](https://en.wikipedia.org/wiki/ISO_8601#Dates).
Together, `isoYear` and `isoMonth` must represent a valid month in that calendar.
Together, `isoYear`, `isoMonth`, and `refISODay` must represent a valid date in that calendar.

The range of allowed values for this type is exactly enough that calling [`getYearMonth()`](./date.html#getYearMonth) on any valid `Temporal.Date` will succeed.
If `isoYear` and `isoMonth` are outside of this range, then `constrain` mode will clamp the date to the limit of the allowed range.
Expand Down Expand Up @@ -446,7 +448,7 @@ Object.assign({}, ym).year // => undefined
Object.assign({}, ym.getFields()).year // => 2019
```

### yearMonth.**getISOCalendarFields**(): { year: number, month: number }
### yearMonth.**getISOCalendarFields**(): { year: number, month: number, refISODay: number }

**Returns:** a plain object with properties expressing `yearMonth` in the ISO 8601 calendar.

Expand Down
2 changes: 2 additions & 0 deletions polyfill/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ export namespace Temporal {
export type MonthDayISOCalendarFields = {
month: number;
day: number;
refISOYear: number;
};

/**
Expand Down Expand Up @@ -437,6 +438,7 @@ export namespace Temporal {
export type YearMonthISOCalendarFields = {
year: number;
month: number;
refISODay: number;
};

/**
Expand Down
22 changes: 13 additions & 9 deletions polyfill/lib/ecmascript.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import {
MILLISECOND,
MICROSECOND,
NANOSECOND,
REF_ISO_YEAR,
REF_ISO_DAY,
YEARS,
MONTHS,
DAYS,
Expand Down Expand Up @@ -52,8 +54,8 @@ export const ES = ObjectAssign({}, ES2019, {
!HasSlot(item, ISO_YEAR, ISO_MONTH, ISO_DAY),
IsTemporalDateTime: (item) =>
HasSlot(item, ISO_YEAR, ISO_MONTH, ISO_DAY, HOUR, MINUTE, SECOND, MILLISECOND, MICROSECOND, NANOSECOND),
IsTemporalYearMonth: (item) => HasSlot(item, ISO_YEAR, ISO_MONTH) && !HasSlot(item, ISO_DAY),
IsTemporalMonthDay: (item) => HasSlot(item, ISO_MONTH, ISO_DAY) && !HasSlot(item, ISO_YEAR),
IsTemporalYearMonth: (item) => HasSlot(item, ISO_YEAR, ISO_MONTH, REF_ISO_DAY),
IsTemporalMonthDay: (item) => HasSlot(item, ISO_MONTH, ISO_DAY, REF_ISO_YEAR),
ToTemporalTimeZone: (item) => {
if (ES.IsTemporalTimeZone(item)) return item;
const TimeZone = GetIntrinsic('%Temporal.TimeZone%');
Expand Down Expand Up @@ -282,32 +284,33 @@ export const ES = ObjectAssign({}, ES2019, {
return { hour, minute, second, millisecond, microsecond, nanosecond };
},
RegulateYearMonth: (year, month, disambiguation) => {
const refISODay = 1;
switch (disambiguation) {
case 'reject':
ES.RejectYearMonth(year, month);
ES.RejectYearMonth(year, month, refISODay);
break;
case 'constrain':
({ year, month } = ES.ConstrainYearMonth(year, month));
break;
case 'balance':
({ year, month } = ES.BalanceYearMonth(year, month));
// Still rejected if balanced YearMonth is outside valid range
ES.RejectYearMonth(year, month);
ES.RejectYearMonth(year, month, refISODay);
break;
}
return { year, month };
},
RegulateMonthDay: (month, day, disambiguation) => {
const leapYear = 1972;
const refISOYear = 1972;
switch (disambiguation) {
case 'reject':
ES.RejectDate(leapYear, month, day);
ES.RejectDate(refISOYear, month, day);
break;
case 'constrain':
({ month, day } = ES.ConstrainDate(leapYear, month, day));
({ month, day } = ES.ConstrainDate(refISOYear, month, day));
break;
case 'balance':
({ month, day } = ES.BalanceDate(leapYear, month, day));
({ month, day } = ES.BalanceDate(refISOYear, month, day));
break;
}
return { month, day };
Expand Down Expand Up @@ -1034,7 +1037,7 @@ export const ES = ObjectAssign({}, ES2019, {
throw new RangeError('Absolute outside of supported range');
}
},
RejectYearMonth: (year, month) => {
RejectYearMonth: (year, month, refISODay) => {
ES.RejectToRange(year, YEAR_MIN, YEAR_MAX);
if (year === YEAR_MIN) {
ES.RejectToRange(month, 4, 12);
Expand All @@ -1043,6 +1046,7 @@ export const ES = ObjectAssign({}, ES2019, {
} else {
ES.RejectToRange(month, 1, 12);
}
ES.RejectToRange(refISODay, 1, ES.DaysInMonth(year, month));
},

DifferenceDate: (smaller, larger, largestUnit = 'days') => {
Expand Down
19 changes: 12 additions & 7 deletions polyfill/lib/monthday.mjs
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { ES } from './ecmascript.mjs';
import { GetIntrinsic, MakeIntrinsicClass } from './intrinsicclass.mjs';
import { ISO_MONTH, ISO_DAY, CreateSlots, GetSlot, SetSlot } from './slots.mjs';
import { ISO_MONTH, ISO_DAY, REF_ISO_YEAR, CreateSlots, GetSlot, SetSlot } from './slots.mjs';

export class MonthDay {
constructor(isoMonth, isoDay) {
constructor(isoMonth, isoDay, refISOYear = 1972) {
isoMonth = ES.ToInteger(isoMonth);
isoDay = ES.ToInteger(isoDay);
const leapYear = 1972; // XXX #261 leap year
ES.RejectDate(leapYear, isoMonth, isoDay);
refISOYear = ES.ToInteger(refISOYear);
ES.RejectDate(refISOYear, isoMonth, isoDay);

CreateSlots(this);
SetSlot(this, ISO_MONTH, isoMonth);
SetSlot(this, ISO_DAY, isoDay);
SetSlot(this, REF_ISO_YEAR, refISOYear);
}

get month() {
Expand Down Expand Up @@ -80,25 +81,29 @@ export class MonthDay {
if (!ES.IsTemporalMonthDay(this)) throw new TypeError('invalid receiver');
return {
month: GetSlot(this, ISO_MONTH),
day: GetSlot(this, ISO_DAY)
day: GetSlot(this, ISO_DAY),
refISOYear: GetSlot(this, REF_ISO_YEAR)
};
}
static from(item, options = undefined) {
const disambiguation = ES.ToTemporalDisambiguation(options);
let month, day;
let month, day, refISOYear;
if (typeof item === 'object' && item) {
if (ES.IsTemporalMonthDay(item)) {
month = GetSlot(item, ISO_MONTH);
day = GetSlot(item, ISO_DAY);
refISOYear = GetSlot(item, REF_ISO_YEAR);
} else {
// Intentionally alphabetical
({ month, day } = ES.ToRecord(item, [['day'], ['month']]));
refISOYear = 1972;
}
} else {
({ month, day } = ES.ParseTemporalMonthDayString(ES.ToString(item)));
refISOYear = 1972;
}
({ month, day } = ES.RegulateMonthDay(month, day, disambiguation));
const result = new this(month, day);
const result = new this(month, day, refISOYear);
if (!ES.IsTemporalMonthDay(result)) throw new TypeError('invalid result');
return result;
}
Expand Down
2 changes: 2 additions & 0 deletions polyfill/lib/slots.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export const SECOND = 'slot-second';
export const MILLISECOND = 'slot-millisecond';
export const MICROSECOND = 'slot-microsecond';
export const NANOSECOND = 'slot-nanosecond';
export const REF_ISO_YEAR = 'slot-ref-iso-year';
export const REF_ISO_DAY = 'slot-ref-iso-day';

// Duration
export const YEARS = 'slot-years';
Expand Down
18 changes: 12 additions & 6 deletions polyfill/lib/yearmonth.mjs
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { ES } from './ecmascript.mjs';
import { GetIntrinsic, MakeIntrinsicClass } from './intrinsicclass.mjs';
import { ISO_YEAR, ISO_MONTH, CreateSlots, GetSlot, SetSlot } from './slots.mjs';
import { ISO_YEAR, ISO_MONTH, REF_ISO_DAY, CreateSlots, GetSlot, SetSlot } from './slots.mjs';

export class YearMonth {
constructor(isoYear, isoMonth) {
constructor(isoYear, isoMonth, refISODay = 1) {
isoYear = ES.ToInteger(isoYear);
isoMonth = ES.ToInteger(isoMonth);
ES.RejectYearMonth(isoYear, isoMonth);
refISODay = ES.ToInteger(refISODay);
ES.RejectYearMonth(isoYear, isoMonth, refISODay);
CreateSlots(this);
SetSlot(this, ISO_YEAR, isoYear);
SetSlot(this, ISO_MONTH, isoMonth);
SetSlot(this, REF_ISO_DAY, refISODay);
}
get year() {
if (!ES.IsTemporalYearMonth(this)) throw new TypeError('invalid receiver');
Expand Down Expand Up @@ -149,25 +151,29 @@ export class YearMonth {
if (!ES.IsTemporalYearMonth(this)) throw new TypeError('invalid receiver');
return {
year: GetSlot(this, ISO_YEAR),
month: GetSlot(this, ISO_MONTH)
month: GetSlot(this, ISO_MONTH),
refISODay: GetSlot(this, REF_ISO_DAY)
};
}
static from(item, options = undefined) {
const disambiguation = ES.ToTemporalDisambiguation(options);
let year, month;
let year, month, refISODay;
if (typeof item === 'object' && item) {
if (ES.IsTemporalYearMonth(item)) {
year = GetSlot(item, ISO_YEAR);
month = GetSlot(item, ISO_MONTH);
refISODay = GetSlot(item, REF_ISO_DAY);
} else {
// Intentionally alphabetical
({ year, month } = ES.ToRecord(item, [['month'], ['year']]));
refISODay = 1;
}
} else {
({ year, month } = ES.ParseTemporalYearMonthString(ES.ToString(item)));
refISODay = 1;
}
({ year, month } = ES.RegulateYearMonth(year, month, disambiguation));
const result = new this(year, month);
const result = new this(year, month, refISODay);
if (!ES.IsTemporalYearMonth(result)) throw new TypeError('invalid result');
return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ esid: sec-temporal.monthday

assert.throws(RangeError, () => new Temporal.MonthDay(Infinity, 1));
assert.throws(RangeError, () => new Temporal.MonthDay(1, Infinity));
assert.throws(RangeError, () => new Temporal.MonthDay(1, 1, Infinity));

let calls = 0;
const obj = {
Expand All @@ -21,3 +22,5 @@ assert.throws(RangeError, () => new Temporal.MonthDay(obj, 1));
assert.sameValue(calls, 1, "it fails after fetching the primitive value");
assert.throws(RangeError, () => new Temporal.MonthDay(1, obj));
assert.sameValue(calls, 2, "it fails after fetching the primitive value");
assert.throws(RangeError, () => new Temporal.MonthDay(1, 1, obj));
assert.sameValue(calls, 3, "it fails after fetching the primitive value");
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ esid: sec-temporal.monthday

assert.throws(RangeError, () => new Temporal.MonthDay(-Infinity, 1));
assert.throws(RangeError, () => new Temporal.MonthDay(1, -Infinity));
assert.throws(RangeError, () => new Temporal.MonthDay(1, 1, -Infinity));

let calls = 0;
const obj = {
Expand All @@ -21,3 +22,5 @@ assert.throws(RangeError, () => new Temporal.MonthDay(obj, 1));
assert.sameValue(calls, 1, "it fails after fetching the primitive value");
assert.throws(RangeError, () => new Temporal.MonthDay(1, obj));
assert.sameValue(calls, 2, "it fails after fetching the primitive value");
assert.throws(RangeError, () => new Temporal.MonthDay(1, 1, obj));
assert.sameValue(calls, 3, "it fails after fetching the primitive value");
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ esid: sec-temporal.yearmonth

assert.throws(RangeError, () => new Temporal.YearMonth(Infinity, 1));
assert.throws(RangeError, () => new Temporal.YearMonth(1970, Infinity));
assert.throws(RangeError, () => new Temporal.YearMonth(1970, 1, Infinity));

let calls = 0;
const obj = {
Expand All @@ -21,3 +22,5 @@ assert.throws(RangeError, () => new Temporal.YearMonth(obj, 1));
assert.sameValue(calls, 1, "it fails after fetching the primitive value");
assert.throws(RangeError, () => new Temporal.YearMonth(1970, obj));
assert.sameValue(calls, 2, "it fails after fetching the primitive value");
assert.throws(RangeError, () => new Temporal.YearMonth(1970, 1, obj));
assert.sameValue(calls, 3, "it fails after fetching the primitive value");
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ esid: sec-temporal.yearmonth

assert.throws(RangeError, () => new Temporal.YearMonth(-Infinity, 1));
assert.throws(RangeError, () => new Temporal.YearMonth(1970, -Infinity));
assert.throws(RangeError, () => new Temporal.YearMonth(1970, 1, -Infinity));

let calls = 0;
const obj = {
Expand All @@ -21,3 +22,5 @@ assert.throws(RangeError, () => new Temporal.YearMonth(obj, 1));
assert.sameValue(calls, 1, "it fails after fetching the primitive value");
assert.throws(RangeError, () => new Temporal.YearMonth(1970, obj));
assert.sameValue(calls, 2, "it fails after fetching the primitive value");
assert.throws(RangeError, () => new Temporal.YearMonth(1970, 1, obj));
assert.sameValue(calls, 3, "it fails after fetching the primitive value");
2 changes: 2 additions & 0 deletions polyfill/test/monthday.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -182,11 +182,13 @@ describe('MonthDay', () => {
it('fields', () => {
equal(fields.month, 11);
equal(fields.day, 18);
equal(typeof fields.refISOYear, 'number');
});
it('enumerable', () => {
const fields2 = { ...fields };
equal(fields2.month, 11);
equal(fields2.day, 18);
equal(typeof fields2.refISOYear, 'number');
});
it('as input to from()', () => {
const md2 = MonthDay.from(fields);
Expand Down
2 changes: 2 additions & 0 deletions polyfill/test/yearmonth.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -357,11 +357,13 @@ describe('YearMonth', () => {
it('fields', () => {
equal(fields.year, 1976);
equal(fields.month, 11);
equal(typeof fields.refISODay, 'number');
});
it('enumerable', () => {
const fields2 = { ...fields };
equal(fields2.year, 1976);
equal(fields2.month, 11);
equal(typeof fields2.refISODay, 'number');
});
it('as input to from()', () => {
const ym2 = YearMonth.from(fields);
Expand Down
Loading

0 comments on commit b6347fd

Please sign in to comment.