Skip to content

Commit

Permalink
Add Calendar.fields method
Browse files Browse the repository at this point in the history
The Calendar.fields method is called whenever Temporal needs to determine
if a calendar object requires extra fields to uniquely identify its date.

Closes: #666
  • Loading branch information
ptomato committed Oct 22, 2020
1 parent b3bd658 commit 9dcf449
Show file tree
Hide file tree
Showing 16 changed files with 473 additions and 338 deletions.
7 changes: 7 additions & 0 deletions docs/calendar-draft.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ class Temporal.Calendar {
/** A string identifier for this calendar */
id : string;

fields(
fields: array<string>
) : array<string>;

//////////////////
// Arithmetic //
//////////////////
Expand Down Expand Up @@ -151,6 +155,9 @@ get foo(...args) {
Calendars can add additional *calendar-specific accessors*, such as the year type ("kesidran", "chaser", "maleh") in the Hebrew calendar, and may add conforming accessor methods to Temporal.Date.prototype.
If any of these accessors are needed for constructing a Temporal.Date from fields, then the calendar should implement `fields()` which, given an array of field names in the ISO calendar, returns an array of equivalent field names in the calendar.
We are not aware of this being necessary for any built-in calendars.
An instance of `MyCalendar` is *expected* to have stateless behavior; i.e., calling a method with the same arguments should return the same result each time. There would be no mechanism for enforcing that user-land calendars are stateless; the calendar author should test this expectation on their own in order to prevent unexpected behavior such as the lack of round-tripping.
### Enumerable Properties
Expand Down
22 changes: 22 additions & 0 deletions docs/calendar.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,28 @@ Temporal.Calendar.from('chinese').dateDifference(
) // => P1M2D
```

### calendar.**fields**(fields: array<string>) : array<string>

**Parameters:**

- `fields` (array of strings): A list of field names.

**Returns:** a new list of field names.

This method does not need to be called directly except in specialized code.
It is called indirectly when using the `from()` static methods and `with()` methods of `Temporal.DateTime`, `Temporal.Date`, and `Temporal.YearMonth`.

Custom calendars should override this method if they require more fields with which to denote the date than the standard `era`, `year`, `month`, and `day`.
The input array contains the field names that are necessary for a particular operation (for example, `'month'` and `'day'` for `Temporal.MonthDay.prototype.with()`), and the method should make a copy of the array and add whichever extra fields are necessary.

Usage example:

```js
// In built-in calendars, this method just makes a copy of the input array
Temporal.Calendar.from('iso8601').fields(['month', 'day']);
// => ['month', 'day']
```

### calendar.**toString**() : string

**Returns:** The string given by `calendar.id`.
Expand Down
2 changes: 2 additions & 0 deletions polyfill/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,7 @@ export namespace Temporal {
| /** @deprecated */ 'day'
>
): Temporal.Duration;
fields?(fields: Array<string>): Array<string>;
}

/**
Expand Down Expand Up @@ -550,6 +551,7 @@ export namespace Temporal {
| /** @deprecated */ 'day'
>
): Temporal.Duration;
fields(fields: Array<string>): Array<string>;
toString(): string;
}

Expand Down
11 changes: 8 additions & 3 deletions polyfill/lib/calendar.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ export class Calendar {
void constructor;
throw new Error('not implemented');
}
fields(fields) {
if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver');
return ES.CreateListFromArrayLike(fields, ['String']);
}
dateAdd(date, duration, options, constructor) {
void date;
void duration;
Expand Down Expand Up @@ -137,6 +141,7 @@ export class Calendar {

MakeIntrinsicClass(Calendar, 'Temporal.Calendar');
DefineIntrinsic('Temporal.Calendar.from', Calendar.from);
DefineIntrinsic('Temporal.Calendar.prototype.fields', Calendar.prototype.fields);
DefineIntrinsic('Temporal.Calendar.prototype.toString', Calendar.prototype.toString);

class ISO8601Calendar extends Calendar {
Expand All @@ -149,23 +154,23 @@ class ISO8601Calendar extends Calendar {
if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver');
options = ES.NormalizeOptionsObject(options);
const overflow = ES.ToTemporalOverflow(options);
let { year, month, day } = ES.ToTemporalDateRecord(fields);
let { year, month, day } = ES.ToRecord(fields, [['day'], ['month'], ['year']]);
({ year, month, day } = ES.RegulateDate(year, month, day, overflow));
return new constructor(year, month, day, this);
}
yearMonthFromFields(fields, options, constructor) {
if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver');
options = ES.NormalizeOptionsObject(options);
const overflow = ES.ToTemporalOverflow(options);
let { year, month } = ES.ToTemporalYearMonthRecord(fields);
let { year, month } = ES.ToRecord(fields, [['month'], ['year']]);
({ year, month } = ES.RegulateYearMonth(year, month, overflow));
return new constructor(year, month, this, /* referenceISODay = */ 1);
}
monthDayFromFields(fields, options, constructor) {
if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver');
options = ES.NormalizeOptionsObject(options);
const overflow = ES.ToTemporalOverflow(options);
let { month, day } = ES.ToTemporalMonthDayRecord(fields);
let { month, day } = ES.ToRecord(fields, [['day'], ['month']]);
({ month, day } = ES.RegulateMonthDay(month, day, overflow));
return new constructor(month, day, this, /* referenceISOYear = */ 1972);
}
Expand Down
19 changes: 12 additions & 7 deletions polyfill/lib/date.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,12 @@ export class Date {
calendar = GetSlot(this, CALENDAR);
source = this;
}
const props = ES.ToPartialRecord(temporalDateLike, ['day', 'era', 'month', 'year']);
const fieldNames = ES.CalendarFields(calendar, ['day', 'era', 'month', 'year']);
const props = ES.ToPartialRecord(temporalDateLike, fieldNames);
if (!props) {
throw new TypeError('invalid date-like');
}
const fields = ES.ToTemporalDateRecord(source);
const fields = ES.ToTemporalDateFields(source, fieldNames);
ObjectAssign(fields, props);
const Construct = ES.SpeciesConstructor(this, Date);
const result = calendar.dateFromFields(fields, options, Construct);
Expand Down Expand Up @@ -273,20 +274,24 @@ export class Date {
if (!ES.IsTemporalDate(this)) throw new TypeError('invalid receiver');
const YearMonth = GetIntrinsic('%Temporal.YearMonth%');
const calendar = GetSlot(this, CALENDAR);
const fields = ES.ToTemporalDateRecord(this);
const fieldNames = ES.CalendarFields(calendar, ['day', 'era', 'month', 'year']);
const fields = ES.ToTemporalDateFields(this, fieldNames);
return calendar.yearMonthFromFields(fields, {}, YearMonth);
}
toMonthDay() {
if (!ES.IsTemporalDate(this)) throw new TypeError('invalid receiver');
const MonthDay = GetIntrinsic('%Temporal.MonthDay%');
const calendar = GetSlot(this, CALENDAR);
const fields = ES.ToTemporalDateRecord(this);
const fieldNames = ES.CalendarFields(calendar, ['day', 'era', 'month', 'year']);
const fields = ES.ToTemporalDateFields(this, fieldNames);
return calendar.monthDayFromFields(fields, {}, MonthDay);
}
getFields() {
const fields = ES.ToTemporalDateRecord(this);
if (!fields) throw new TypeError('invalid receiver');
fields.calendar = GetSlot(this, CALENDAR);
if (!ES.IsTemporalDate(this)) throw new TypeError('invalid receiver');
const calendar = GetSlot(this, CALENDAR);
const fieldNames = ES.CalendarFields(calendar, ['day', 'era', 'month', 'year']);
const fields = ES.ToTemporalDateFields(this, fieldNames);
fields.calendar = calendar;
return fields;
}
getISOFields() {
Expand Down
19 changes: 12 additions & 7 deletions polyfill/lib/datetime.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ export class DateTime {
calendar = GetSlot(this, CALENDAR);
source = this;
}
const props = ES.ToPartialRecord(temporalDateTimeLike, [
const fieldNames = ES.CalendarFields(calendar, [
'day',
'era',
'hour',
Expand All @@ -227,10 +227,11 @@ export class DateTime {
'second',
'year'
]);
const props = ES.ToPartialRecord(temporalDateTimeLike, fieldNames);
if (!props) {
throw new TypeError('invalid date-time-like');
}
const fields = ES.ToTemporalDateTimeRecord(source);
const fields = ES.ToTemporalDateTimeFields(source, fieldNames);
ObjectAssign(fields, props);
const date = calendar.dateFromFields(fields, options, GetIntrinsic('%Temporal.Date%'));
let year = GetSlot(date, ISO_YEAR);
Expand Down Expand Up @@ -622,24 +623,28 @@ export class DateTime {
if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver');
const YearMonth = GetIntrinsic('%Temporal.YearMonth%');
const calendar = GetSlot(this, CALENDAR);
const fields = ES.ToTemporalDateTimeRecord(this);
const fieldNames = ES.CalendarFields(calendar, ['day', 'era', 'month', 'year']);
const fields = ES.ToTemporalDateFields(this, fieldNames);
return calendar.yearMonthFromFields(fields, {}, YearMonth);
}
toMonthDay() {
if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver');
const MonthDay = GetIntrinsic('%Temporal.MonthDay%');
const calendar = GetSlot(this, CALENDAR);
const fields = ES.ToTemporalDateTimeRecord(this);
const fieldNames = ES.CalendarFields(calendar, ['day', 'era', 'month', 'year']);
const fields = ES.ToTemporalDateFields(this, fieldNames);
return calendar.monthDayFromFields(fields, {}, MonthDay);
}
toTime() {
if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver');
return ES.TemporalDateTimeToTime(this);
}
getFields() {
const fields = ES.ToTemporalDateTimeRecord(this);
if (!fields) throw new TypeError('invalid receiver');
fields.calendar = GetSlot(this, CALENDAR);
if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver');
const calendar = GetSlot(this, CALENDAR);
const fieldNames = ES.CalendarFields(calendar, ['day', 'era', 'month', 'year']);
const fields = ES.ToTemporalDateTimeFields(this, fieldNames);
fields.calendar = calendar;
return fields;
}
getISOFields() {
Expand Down
Loading

0 comments on commit 9dcf449

Please sign in to comment.