diff --git a/docs/calendar.md b/docs/calendar.md index 77e894da5b..80766240c9 100644 --- a/docs/calendar.md +++ b/docs/calendar.md @@ -47,6 +47,7 @@ The identifier of a custom calendar must consist of one or more components of be It's also possible for a plain object to be a custom calendar, without subclassing. The object must implement the `Temporal.Calendar` methods and have an `id` property. +It must not have a `calendar` property, so that it can be distinguished in `Temporal.Calendar.from()` from other Temporal objects that have a calendar. It is possible to pass such an object into any Temporal API that would normally take a built-in `Temporal.Calendar`. ## Constructor @@ -76,12 +77,13 @@ cal = new Temporal.Calendar('gregory'); ### Temporal.Calendar.**from**(_thing_: any) : Temporal.Calendar **Parameters:** -- `thing`: A `Temporal.Calendar` object or a value from which to create a `Temporal.Calendar`. +- `thing`: A calendar object, a Temporal object that carries a calendar, or a value from which to create a `Temporal.Calendar`. -**Returns:** a new `Temporal.Calendar` object. +**Returns:** a calendar object. This static method creates a new calendar from another value. -If the value is another `Temporal.Calendar` object, a new object representing the same calendar is returned. +If the value is another `Temporal.Calendar` object, or object implementing the calendar protocol, the same object is returned. +If the value is another Temporal object that carries a calendar or an object with a `calendar` property, such as a `Temporal.ZonedDateTime`, the object's calendar is returned. Any other value is converted to a string, which is expected to be either: - a string that is accepted by `new Temporal.Calendar()`; or diff --git a/polyfill/index.d.ts b/polyfill/index.d.ts index 15b6cf9b1f..1dee9ae5ff 100644 --- a/polyfill/index.d.ts +++ b/polyfill/index.d.ts @@ -347,6 +347,7 @@ export namespace Temporal { export interface CalendarProtocol { id: string; + calendar?: never; year(date: Temporal.Date): number; month(date: Temporal.Date): number; day(date: Temporal.Date): number; @@ -409,7 +410,7 @@ export namespace Temporal { * * See https://tc39.es/proposal-temporal/docs/calendar.html for more details. */ - export class Calendar implements Required { + export class Calendar implements Omit, 'calendar'> { static from(item: CalendarProtocol | string): Temporal.Calendar; constructor(calendarIdentifier: string); readonly id: string; diff --git a/polyfill/lib/calendar.mjs b/polyfill/lib/calendar.mjs index bae93c9c05..c70958db82 100644 --- a/polyfill/lib/calendar.mjs +++ b/polyfill/lib/calendar.mjs @@ -117,7 +117,11 @@ export class Calendar { return GetSlot(this, CALENDAR_ID); } static from(item) { - if (ES.Type(item) === 'Object') return item; + if (ES.Type(item) === 'Object') { + if (!('calendar' in item)) return item; + item = item.calendar; + if (ES.Type(item) === 'Object' && !('calendar' in item)) return item; + } const stringIdent = ES.ToString(item); if (IsBuiltinCalendar(stringIdent)) return GetBuiltinCalendar(stringIdent); let calendar; diff --git a/polyfill/test/calendar.mjs b/polyfill/test/calendar.mjs index 4939c08167..6feae35aa4 100644 --- a/polyfill/test/calendar.mjs +++ b/polyfill/test/calendar.mjs @@ -100,10 +100,42 @@ describe('Calendar', () => { it(`Calendar.from(${id}) is a calendar`, () => assert(calendar instanceof Calendar)); it(`Calendar.from(${id}) has the correct ID`, () => equal(calendar.id, id)); } - it('Calendar.from throws with bad identifier', () => { - throws(() => Calendar.from('local')); - throws(() => Calendar.from('iso-8601')); - throws(() => Calendar.from('[c=iso8601]')); + it('other types with a calendar are accepted', () => { + [ + Temporal.Date.from('1976-11-18[c=gregory]'), + Temporal.DateTime.from('1976-11-18[c=gregory]'), + Temporal.MonthDay.from('1972-11-18[c=gregory]'), + Temporal.YearMonth.from('1976-11-01[c=gregory]') + ].forEach((obj) => { + const calFrom = Calendar.from(obj); + assert(calFrom instanceof Calendar); + equal(calFrom.id, 'gregory'); + }); + }); + it('property bag with calendar object is accepted', () => { + const cal = new Calendar('iso8601'); + const calFrom = Calendar.from({ calendar: cal }); + assert(calFrom instanceof Calendar); + equal(calFrom.id, 'iso8601'); + }); + it('property bag with string is accepted', () => { + const calFrom = Calendar.from({ calendar: 'iso8601' }); + assert(calFrom instanceof Calendar); + equal(calFrom.id, 'iso8601'); + }); + it('property bag with custom calendar is accepted', () => { + const custom = { id: 'custom-calendar' }; + const calFrom = Calendar.from({ calendar: custom }); + equal(calFrom, custom); + }); + it('throws with bad identifier', () => { + throws(() => Calendar.from('local'), RangeError); + throws(() => Calendar.from('iso-8601'), RangeError); + throws(() => Calendar.from('[c=iso8601]'), RangeError); + }); + it('throws with bad value in property bag', () => { + throws(() => Calendar.from({ calendar: 'local' }), RangeError); + throws(() => Calendar.from({ calendar: { calendar: 'iso8601' } }), RangeError); }); }); describe('Calendar.from(ISO string)', () => { diff --git a/spec/calendar.html b/spec/calendar.html index 520ff60ac5..e6276bd5e7 100644 --- a/spec/calendar.html +++ b/spec/calendar.html @@ -171,7 +171,9 @@

Temporal.Calendar.from ( _item_ )

1. If Type(_item_) is Object, then - 1. Return _item_. + 1. If ? HasProperty(_item_, *"calendar"*) is *false*, return _item_. + 1. Set _item_ to ? Get(_item_, *"calendar"*). + 1. If Type(_item_) is Object and ? HasProperty(_item_, *"calendar"*) is *false*, return _item_. 1. Let _string_ be ? ToString(_item_). 1. If ! IsBuiltinCalendar(_string_) is *false*, then 1. Let _string_ be ? ParseTemporalCalendarString(_string_).