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_).