Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calendar.fields method #1014

Merged
merged 3 commits into from
Oct 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
24 changes: 24 additions & 0 deletions docs/calendar.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,30 @@ 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`.
Ms2ger marked this conversation as resolved.
Show resolved Hide resolved
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.

The default implementation of this method returns a copy of `fields`.

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