Skip to content

Commit

Permalink
Allow property bags in Temporal.Calendar.from()
Browse files Browse the repository at this point in the history
These are kept distinct from plain-object custom calendars by the absence
of a 'calendar' property. This way, any Temporal type that carries a
calendar, as well as any property bag of that type, can be passed to
Temporal.Calendar.from().

See: #925
  • Loading branch information
ptomato authored and Ms2ger committed Oct 21, 2020
1 parent 013a99b commit 660cd1b
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 10 deletions.
8 changes: 5 additions & 3 deletions docs/calendar.md
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion polyfill/index.d.ts
Expand Up @@ -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;
Expand Down Expand Up @@ -409,7 +410,7 @@ export namespace Temporal {
*
* See https://tc39.es/proposal-temporal/docs/calendar.html for more details.
*/
export class Calendar implements Required<CalendarProtocol> {
export class Calendar implements Omit<Required<CalendarProtocol>, 'calendar'> {
static from(item: CalendarProtocol | string): Temporal.Calendar;
constructor(calendarIdentifier: string);
readonly id: string;
Expand Down
6 changes: 5 additions & 1 deletion polyfill/lib/calendar.mjs
Expand Up @@ -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;
Expand Down
40 changes: 36 additions & 4 deletions polyfill/test/calendar.mjs
Expand Up @@ -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)', () => {
Expand Down
4 changes: 3 additions & 1 deletion spec/calendar.html
Expand Up @@ -171,7 +171,9 @@ <h1>Temporal.Calendar.from ( _item_ )</h1>
</p>
<emu-alg>
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_).
Expand Down

0 comments on commit 660cd1b

Please sign in to comment.