Skip to content

Commit

Permalink
Refactor calendars to eliminate multiple parent classes
Browse files Browse the repository at this point in the history
We had originally decided to go with a scheme where the value of the
[[Identifier]] internal slot determines the behaviour of the calendar
methods, and there are not individual parent classes for each built-in
calendar. This implements that scheme.

Merges most of the spec text of ISO8601Calendar into Calendar.

Closes: #847
See: #300
  • Loading branch information
ptomato committed Nov 15, 2020
1 parent 1d4ceb7 commit e4e240e
Show file tree
Hide file tree
Showing 16 changed files with 487 additions and 712 deletions.
442 changes: 186 additions & 256 deletions polyfill/lib/calendar.mjs

Large diffs are not rendered by default.

35 changes: 19 additions & 16 deletions polyfill/lib/ecmascript.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import ToPrimitive from 'es-abstract/2020/ToPrimitive.js';
import ToString from 'es-abstract/2020/ToString.js';
import Type from 'es-abstract/2020/Type.js';

import { GetISO8601Calendar } from './calendar.mjs';
import { GetIntrinsic } from './intrinsicclass.mjs';
import {
GetSlot,
Expand Down Expand Up @@ -677,7 +676,7 @@ export const ES = ObjectAssign({}, ES2020, {
if (ES.Type(relativeTo) === 'Object') {
if (ES.IsTemporalZonedDateTime(relativeTo) || ES.IsTemporalDateTime(relativeTo)) return relativeTo;
calendar = relativeTo.calendar;
if (calendar === undefined) calendar = GetISO8601Calendar();
if (calendar === undefined) calendar = ES.GetISO8601Calendar();
calendar = ES.ToTemporalCalendar(calendar);
const fieldNames = ES.CalendarFields(calendar, [
'day',
Expand Down Expand Up @@ -721,7 +720,7 @@ export const ES = ObjectAssign({}, ES2020, {
offset
} = ES.ParseISODateTime(ES.ToString(relativeTo), { zoneRequired: false }));
if (ianaName) timeZone = ianaName;
if (!calendar) calendar = GetISO8601Calendar();
if (!calendar) calendar = ES.GetISO8601Calendar();
calendar = ES.ToTemporalCalendar(calendar);
}
if (timeZone) {
Expand Down Expand Up @@ -764,7 +763,7 @@ export const ES = ObjectAssign({}, ES2020, {
return new TemporalTime(hour, minute, second, millisecond, microsecond, nanosecond);
}
let { year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, calendar } = props;
if (!calendar) calendar = new (GetIntrinsic('%Temporal.ISO8601Calendar%'))();
if (!calendar) calendar = ES.GetISO8601Calendar();
const DATE_ONLY = new RegExp(`^${PARSE.datesplit.source}$`);
const match = DATE_ONLY.exec(str);
if (match) {
Expand Down Expand Up @@ -972,15 +971,15 @@ export const ES = ObjectAssign({}, ES2020, {
if (ES.Type(item) === 'Object') {
if (ES.IsTemporalDate(item)) return item;
let calendar = item.calendar;
if (calendar === undefined) calendar = new (GetIntrinsic('%Temporal.ISO8601Calendar%'))();
if (calendar === undefined) calendar = ES.GetISO8601Calendar();
calendar = ES.ToTemporalCalendar(calendar);
const fieldNames = ES.CalendarFields(calendar, ['day', 'month', 'year']);
const fields = ES.ToTemporalDateFields(item, fieldNames);
return ES.DateFromFields(calendar, fields, constructor, overflow);
}
let { year, month, day, calendar } = ES.ParseTemporalDateString(ES.ToString(item));
({ year, month, day } = ES.RegulateDate(year, month, day, overflow));
if (calendar === undefined) calendar = new (GetIntrinsic('%Temporal.ISO8601Calendar%'))();
if (calendar === undefined) calendar = ES.GetISO8601Calendar();
calendar = ES.ToTemporalCalendar(calendar);
let result = new constructor(year, month, day, calendar);
if (!ES.IsTemporalDate(result)) throw new TypeError('invalid result');
Expand Down Expand Up @@ -1010,7 +1009,7 @@ export const ES = ObjectAssign({}, ES2020, {
if (ES.IsTemporalDateTime(item)) return item;

calendar = item.calendar;
if (calendar === undefined) calendar = new (GetIntrinsic('%Temporal.ISO8601Calendar%'))();
if (calendar === undefined) calendar = ES.GetISO8601Calendar();
calendar = ES.ToTemporalCalendar(calendar);

const fieldNames = ES.CalendarFields(calendar, ['day', 'month', 'year']);
Expand Down Expand Up @@ -1039,7 +1038,7 @@ export const ES = ObjectAssign({}, ES2020, {
nanosecond,
calendar
} = ES.ParseTemporalDateTimeString(ES.ToString(item)));
if (calendar === undefined) calendar = new (GetIntrinsic('%Temporal.ISO8601Calendar%'))();
if (calendar === undefined) calendar = ES.GetISO8601Calendar();
calendar = ES.ToTemporalCalendar(calendar);
}
const result = new constructor(
Expand Down Expand Up @@ -1113,7 +1112,7 @@ export const ES = ObjectAssign({}, ES2020, {
if (ES.Type(item) === 'Object') {
if (ES.IsTemporalMonthDay(item)) return item;
let calendar = item.calendar;
if (calendar === undefined) calendar = new (GetIntrinsic('%Temporal.ISO8601Calendar%'))();
if (calendar === undefined) calendar = ES.GetISO8601Calendar();
calendar = ES.ToTemporalCalendar(calendar);
const fieldNames = ES.CalendarFields(calendar, ['day', 'month']);
const fields = ES.ToTemporalMonthDayFields(item, fieldNames);
Expand All @@ -1122,7 +1121,7 @@ export const ES = ObjectAssign({}, ES2020, {

let { month, day, referenceISOYear, calendar } = ES.ParseTemporalMonthDayString(ES.ToString(item));
({ month, day } = ES.RegulateMonthDay(month, day, overflow));
if (calendar === undefined) calendar = new (GetIntrinsic('%Temporal.ISO8601Calendar%'))();
if (calendar === undefined) calendar = ES.GetISO8601Calendar();
calendar = ES.ToTemporalCalendar(calendar);
if (referenceISOYear === undefined) referenceISOYear = 1972;
let result = new constructor(month, day, calendar, referenceISOYear);
Expand All @@ -1133,7 +1132,7 @@ export const ES = ObjectAssign({}, ES2020, {
if (ES.Type(item) === 'Object') {
if (ES.IsTemporalTime(item)) return item;
let calendar = item.calendar;
if (calendar === undefined) calendar = new (GetIntrinsic('%Temporal.ISO8601Calendar%'))();
if (calendar === undefined) calendar = ES.GetISO8601Calendar();
calendar = ES.ToTemporalCalendar(calendar);
const fieldNames = ES.CalendarFields(calendar, [
'hour',
Expand All @@ -1159,7 +1158,7 @@ export const ES = ObjectAssign({}, ES2020, {
nanosecond,
overflow
));
if (calendar === undefined) calendar = new (GetIntrinsic('%Temporal.ISO8601Calendar%'))();
if (calendar === undefined) calendar = ES.GetISO8601Calendar();
calendar = ES.ToTemporalCalendar(calendar);
let result = new constructor(hour, minute, second, millisecond, microsecond, nanosecond, calendar);
if (!ES.IsTemporalTime(result)) throw new TypeError('invalid result');
Expand All @@ -1169,7 +1168,7 @@ export const ES = ObjectAssign({}, ES2020, {
if (ES.Type(item) === 'Object') {
if (ES.IsTemporalYearMonth(item)) return item;
let calendar = item.calendar;
if (calendar === undefined) calendar = new (GetIntrinsic('%Temporal.ISO8601Calendar%'))();
if (calendar === undefined) calendar = ES.GetISO8601Calendar();
calendar = ES.ToTemporalCalendar(calendar);
const fieldNames = ES.CalendarFields(calendar, ['month', 'year']);
const fields = ES.ToTemporalYearMonthFields(item, fieldNames);
Expand All @@ -1178,7 +1177,7 @@ export const ES = ObjectAssign({}, ES2020, {

let { year, month, referenceISODay = 1, calendar } = ES.ParseTemporalYearMonthString(ES.ToString(item));
({ year, month } = ES.RegulateYearMonth(year, month, overflow));
if (calendar === undefined) calendar = new (GetIntrinsic('%Temporal.ISO8601Calendar%'))();
if (calendar === undefined) calendar = ES.GetISO8601Calendar();
calendar = ES.ToTemporalCalendar(calendar);
let result = new constructor(year, month, calendar, referenceISODay);
if (!ES.IsTemporalYearMonth(result)) throw new TypeError('invalid result');
Expand Down Expand Up @@ -1258,7 +1257,7 @@ export const ES = ObjectAssign({}, ES2020, {
if (ES.Type(item) === 'Object') {
if (ES.IsTemporalZonedDateTime(item)) return item;
calendar = item.calendar;
if (calendar === undefined) calendar = new (GetIntrinsic('%Temporal.ISO8601Calendar%'))();
if (calendar === undefined) calendar = ES.GetISO8601Calendar();
calendar = ES.ToTemporalCalendar(calendar);
const fieldNames = ES.CalendarFields(calendar, ['day', 'month', 'year']);
const fields = ES.ToTemporalZonedDateTimeFields(item, fieldNames);
Expand Down Expand Up @@ -1294,7 +1293,7 @@ export const ES = ObjectAssign({}, ES2020, {
} = ES.ParseTemporalZonedDateTimeString(ES.ToString(item)));
if (!ianaName) throw new RangeError('time zone ID required in brackets');
timeZone = ES.TimeZoneFrom(ianaName);
if (!calendar) calendar = new (GetIntrinsic('%Temporal.ISO8601Calendar%'))();
if (!calendar) calendar = ES.GetISO8601Calendar();
calendar = ES.ToTemporalCalendar(calendar);
}
let offsetNs = null;
Expand All @@ -1319,6 +1318,10 @@ export const ES = ObjectAssign({}, ES2020, {
return result;
},

GetISO8601Calendar: () => {
const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%');
return new TemporalCalendar('iso8601');
},
CalendarFrom: (calendarLike) => {
const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%');
let from = TemporalCalendar.from;
Expand Down
3 changes: 1 addition & 2 deletions polyfill/lib/instant.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* global __debug__ */

import { GetISO8601Calendar } from './calendar.mjs';
import { ES } from './ecmascript.mjs';
import { DateTimeFormat } from './intl.mjs';
import { GetIntrinsic, MakeIntrinsicClass } from './intrinsicclass.mjs';
Expand Down Expand Up @@ -259,7 +258,7 @@ export class Instant {
}
}
const timeZone = ES.ToTemporalTimeZone(item);
const calendar = GetISO8601Calendar();
const calendar = ES.GetISO8601Calendar();
const TemporalZonedDateTime = GetIntrinsic('%Temporal.ZonedDateTime%');
return new TemporalZonedDateTime(GetSlot(this, EPOCHNANOSECONDS), timeZone, calendar);
}
Expand Down
5 changes: 2 additions & 3 deletions polyfill/lib/now.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { GetISO8601Calendar } from './calendar.mjs';
import { ES } from './ecmascript.mjs';
import { GetIntrinsic } from './intrinsicclass.mjs';

Expand Down Expand Up @@ -27,7 +26,7 @@ function plainDateTime(calendarLike, temporalTimeZoneLike = timeZone()) {
}
function plainDateTimeISO(temporalTimeZoneLike = timeZone()) {
const timeZone = ES.ToTemporalTimeZone(temporalTimeZoneLike);
const calendar = GetISO8601Calendar();
const calendar = ES.GetISO8601Calendar();
const inst = instant();
return ES.GetTemporalDateTimeFor(timeZone, inst, calendar);
}
Expand All @@ -39,7 +38,7 @@ function zonedDateTime(calendarLike, temporalTimeZoneLike = timeZone()) {
}
function zonedDateTimeISO(temporalTimeZoneLike = timeZone()) {
const timeZone = ES.ToTemporalTimeZone(temporalTimeZoneLike);
const calendar = GetISO8601Calendar();
const calendar = ES.GetISO8601Calendar();
const ZonedDateTime = GetIntrinsic('%Temporal.ZonedDateTime%');
return new ZonedDateTime(ES.SystemUTCEpochNanoSeconds(), timeZone, calendar);
}
Expand Down
3 changes: 1 addition & 2 deletions polyfill/lib/plaindate.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* global __debug__ */

import { GetISO8601Calendar } from './calendar.mjs';
import { ES } from './ecmascript.mjs';
import { DateTimeFormat } from './intl.mjs';
import { GetIntrinsic, MakeIntrinsicClass } from './intrinsicclass.mjs';
Expand Down Expand Up @@ -34,7 +33,7 @@ function TemporalDateToString(date, showCalendar = 'auto') {
}

export class PlainDate {
constructor(isoYear, isoMonth, isoDay, calendar = GetISO8601Calendar()) {
constructor(isoYear, isoMonth, isoDay, calendar = ES.GetISO8601Calendar()) {
isoYear = ES.ToInteger(isoYear);
isoMonth = ES.ToInteger(isoMonth);
isoDay = ES.ToInteger(isoDay);
Expand Down
3 changes: 1 addition & 2 deletions polyfill/lib/plaindatetime.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* global __debug__ */

import { GetISO8601Calendar } from './calendar.mjs';
import { ES } from './ecmascript.mjs';
import { DateTimeFormat } from './intl.mjs';
import { GetIntrinsic, MakeIntrinsicClass } from './intrinsicclass.mjs';
Expand Down Expand Up @@ -75,7 +74,7 @@ export class PlainDateTime {
millisecond = 0,
microsecond = 0,
nanosecond = 0,
calendar = GetISO8601Calendar()
calendar = ES.GetISO8601Calendar()
) {
isoYear = ES.ToInteger(isoYear);
isoMonth = ES.ToInteger(isoMonth);
Expand Down
3 changes: 1 addition & 2 deletions polyfill/lib/plainmonthday.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* global __debug__ */

import { GetISO8601Calendar } from './calendar.mjs';
import { ES } from './ecmascript.mjs';
import { DateTimeFormat } from './intl.mjs';
import { GetIntrinsic, MakeIntrinsicClass } from './intrinsicclass.mjs';
Expand All @@ -24,7 +23,7 @@ function MonthDayToString(monthDay, showCalendar = 'auto') {
}

export class PlainMonthDay {
constructor(isoMonth, isoDay, calendar = GetISO8601Calendar(), referenceISOYear = 1972) {
constructor(isoMonth, isoDay, calendar = ES.GetISO8601Calendar(), referenceISOYear = 1972) {
isoMonth = ES.ToInteger(isoMonth);
isoDay = ES.ToInteger(isoDay);
calendar = ES.ToTemporalCalendar(calendar);
Expand Down
3 changes: 1 addition & 2 deletions polyfill/lib/plaintime.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* global __debug__ */

import { GetISO8601Calendar } from './calendar.mjs';
import { ES } from './ecmascript.mjs';
import { DateTimeFormat } from './intl.mjs';
import { GetIntrinsic, MakeIntrinsicClass } from './intrinsicclass.mjs';
Expand Down Expand Up @@ -63,7 +62,7 @@ export class PlainTime {
isoMillisecond = 0,
isoMicrosecond = 0,
isoNanosecond = 0,
calendar = GetISO8601Calendar()
calendar = ES.GetISO8601Calendar()
) {
isoHour = ES.ToInteger(isoHour);
isoMinute = ES.ToInteger(isoMinute);
Expand Down
3 changes: 1 addition & 2 deletions polyfill/lib/plainyearmonth.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* global __debug__ */

import { GetISO8601Calendar } from './calendar.mjs';
import { ES } from './ecmascript.mjs';
import { DateTimeFormat } from './intl.mjs';
import { GetIntrinsic, MakeIntrinsicClass } from './intrinsicclass.mjs';
Expand All @@ -24,7 +23,7 @@ function YearMonthToString(yearMonth, showCalendar = 'auto') {
}

export class PlainYearMonth {
constructor(isoYear, isoMonth, calendar = GetISO8601Calendar(), referenceISODay = 1) {
constructor(isoYear, isoMonth, calendar = ES.GetISO8601Calendar(), referenceISODay = 1) {
isoYear = ES.ToInteger(isoYear);
isoMonth = ES.ToInteger(isoMonth);
calendar = ES.ToTemporalCalendar(calendar);
Expand Down
3 changes: 1 addition & 2 deletions polyfill/lib/timezone.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* global __debug__ */

import { GetISO8601Calendar } from './calendar.mjs';
import { ES } from './ecmascript.mjs';
import { GetIntrinsic, MakeIntrinsicClass, DefineIntrinsic } from './intrinsicclass.mjs';
import {
Expand Down Expand Up @@ -62,7 +61,7 @@ export class TimeZone {
const offsetNs = ES.GetOffsetNanosecondsFor(this, instant);
return ES.FormatTimeZoneOffsetString(offsetNs);
}
getPlainDateTimeFor(instant, calendar = GetISO8601Calendar()) {
getPlainDateTimeFor(instant, calendar = ES.GetISO8601Calendar()) {
instant = ES.ToTemporalInstant(instant, GetIntrinsic('%Temporal.Instant%'));
calendar = ES.ToTemporalCalendar(calendar);

Expand Down
3 changes: 1 addition & 2 deletions polyfill/lib/zoneddatetime.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* global __debug__ */

import { GetISO8601Calendar } from './calendar.mjs';
import { ES } from './ecmascript.mjs';
import { DateTimeFormat } from './intl.mjs';
import { GetIntrinsic, MakeIntrinsicClass } from './intrinsicclass.mjs';
Expand Down Expand Up @@ -29,7 +28,7 @@ const ArrayPrototypePush = Array.prototype.push;
const ObjectAssign = Object.assign;

export class ZonedDateTime {
constructor(epochNanoseconds, timeZone, calendar = GetISO8601Calendar()) {
constructor(epochNanoseconds, timeZone, calendar = ES.GetISO8601Calendar()) {
epochNanoseconds = ES.ToBigInt(epochNanoseconds);
timeZone = ES.ToTemporalTimeZone(timeZone);
calendar = ES.ToTemporalCalendar(calendar);
Expand Down
27 changes: 16 additions & 11 deletions polyfill/test/regex.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Demitasse from '@pipobscure/demitasse';
const { describe, it, report } = Demitasse;
const { after, before, describe, it, report } = Demitasse;

import Pretty from '@pipobscure/demitasse-pretty';
const { reporter } = Pretty;
Expand Down Expand Up @@ -649,30 +649,35 @@ describe('fromString regex', () => {
});

describe('calendar ID', () => {
function makeCustomCalendar(id) {
return class extends Temporal.Calendar {
constructor() {
super(id);
}
let oldTemporalCalendarFrom = Temporal.Calendar.from;
let fromCalledWith;
before(() => {
Temporal.Calendar.from = function (item) {
fromCalledWith = item;
return new Temporal.Calendar('iso8601');
};
});
function testCalendarID(id) {
return Temporal.PlainDateTime.from(`1970-01-01T00:00[c=${id}]`);
}
describe('valid', () => {
['aaa', 'aaa-aaa', 'eightZZZ', 'eightZZZ-eightZZZ'].forEach((id) => {
it(id, () => {
const Custom = makeCustomCalendar(id);
const calendar = new Custom();
equal(calendar.id, id);
testCalendarID(id);
equal(fromCalledWith, id);
});
});
});
describe('not valid', () => {
['a', 'a-a', 'aa', 'aa-aa', 'foo_', 'foo.', 'ninechars', 'ninechars-ninechars'].forEach((id) => {
it(id, () => {
const Custom = makeCustomCalendar(id);
throws(() => new Custom(), RangeError);
throws(() => testCalendarID(id), RangeError);
});
});
});
after(() => {
Temporal.Calendar.from = oldTemporalCalendarFrom;
});
});
});

Expand Down
16 changes: 10 additions & 6 deletions polyfill/test/usercalendar.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ describe('Userland calendar', () => {
describe('Trivial subclass', () => {
// For the purposes of testing, a nonsensical calendar that uses 0-based
// month numbers, like legacy Date
const ISO8601Calendar = Temporal.Calendar.from('iso8601').constructor;
class ZeroBasedCalendar extends ISO8601Calendar {
class ZeroBasedCalendar extends Temporal.Calendar {
constructor() {
super('zero-based');
super('iso8601');
}
toString() {
return 'zero-based';
}
dateFromFields(fields, options, constructor) {
fields.month++;
Expand Down Expand Up @@ -503,10 +505,9 @@ describe('Userland calendar', () => {
// Contrived example of a calendar identical to the ISO calendar except that
// months are numbered 1, 2, 3, and each year has four seasons of 3 months
// numbered 1, 2, 3, 4.
const ISO8601Calendar = Temporal.Calendar.from('iso8601').constructor;
class SeasonCalendar extends ISO8601Calendar {
class SeasonCalendar extends Temporal.Calendar {
constructor() {
super('season');
super('iso8601');
Object.defineProperty(Temporal.PlainDateTime.prototype, 'season', {
get() {
return this.calendar.season(this);
Expand All @@ -532,6 +533,9 @@ describe('Userland calendar', () => {
configurable: true
});
}
toString() {
return 'season';
}
month(date) {
const { isoMonth } = date.getISOFields();
return ((isoMonth - 1) % 3) + 1;
Expand Down
Loading

0 comments on commit e4e240e

Please sign in to comment.