diff --git a/docs/absolute.md b/docs/absolute.md
index b3582276c7..ab78adddf7 100644
--- a/docs/absolute.md
+++ b/docs/absolute.md
@@ -206,11 +206,11 @@ Same as `getEpochSeconds()`, but with nanosecond (10−9 second)
The value returned from this method is suitable to be passed to `new Temporal.Absolute()`.
-### absolute.**inTimeZone**(_timeZone_: object | string, _calendar_?: Temporal.Calendar | string) : Temporal.DateTime
+### absolute.**inTimeZone**(_timeZone_: object | string, _calendar_?: object | string) : Temporal.DateTime
**Parameters:**
- `timeZone` (object or string): A `Temporal.TimeZone` object, or an object implementing the [time zone protocol](./timezone.md#protocol), or a string description of the time zone; either its IANA name or UTC offset.
-- `calendar` (optional object or string): A `Temporal.Calendar` object, or a calendar identifier.
+- `calendar` (optional object or string): A `Temporal.Calendar` object, or a plain object, or a calendar identifier.
The default is to use the ISO 8601 calendar.
**Returns:** a `Temporal.DateTime` object indicating the calendar date and wall-clock time in `timeZone`, according to the reckoning of `calendar`, at the absolute time indicated by `absolute`.
diff --git a/docs/calendar.md b/docs/calendar.md
index b97441212a..69f161c3bc 100644
--- a/docs/calendar.md
+++ b/docs/calendar.md
@@ -41,6 +41,12 @@ For specialized applications where you need to do calculations in a calendar sys
To do this, create a class inheriting from `Temporal.Calendar`, call `super()` in the constructor with a calendar identifier, and implement all the methods.
Any subclass of `Temporal.Calendar` will be accepted in Temporal APIs where a built-in `Temporal.Calendar` would work.
+### Protocol
+
+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 is possible to pass such an object into any Temporal API that would normally take a built-in `Temporal.Calendar`.
+
## Constructor
### **new Temporal.Calendar**(_calendarIdentifier_: string) : Temporal.Calendar
@@ -97,8 +103,10 @@ cal = Temporal.Calendar.from('2020-01-13T16:31:00.065858086-08:00[America/Vancou
// Existing calendar object
cal2 = Temporal.Calendar.from(cal);
+// Custom calendar that is a plain object (this calendar does not do much)
+cal = Temporal.Calendar.from({id: 'mycalendar'});
+
/*⚠️*/ cal = Temporal.Calendar.from('discordian'); // not a built-in calendar, throws
-/*⚠️*/ cal = Temporal.Calendar.from({id: 'iso8601'}); // not a Calendar object, throws
/*⚠️*/ cal = Temporal.Calendar.from('[c=iso8601]'); // lone annotation not a valid ISO 8601 string
```
diff --git a/docs/date.md b/docs/date.md
index cb91132d0f..8532fcd04f 100644
--- a/docs/date.md
+++ b/docs/date.md
@@ -16,13 +16,13 @@ A `Temporal.Date` can be converted into a `Temporal.DateTime` by combining it wi
## Constructor
-### **new Temporal.Date**(_isoYear_: number, _isoMonth_: number, _isoDay_: number, _calendar_?: Temporal.Calendar) : Temporal.Date
+### **new Temporal.Date**(_isoYear_: number, _isoMonth_: number, _isoDay_: number, _calendar_?: object) : Temporal.Date
**Parameters:**
- `isoYear` (number): A year.
- `isoMonth` (number): A month, ranging between 1 and 12 inclusive.
- `isoDay` (number): A day of the month, ranging between 1 and 31 inclusive.
-- `calendar` (optional `Temporal.Calendar`): A calendar to project the date into.
+- `calendar` (optional `Temporal.Calendar` or plain object): A calendar to project the date into.
**Returns:** a new `Temporal.Date` object.
@@ -145,7 +145,7 @@ date.month // => 8
date.day // => 24
```
-### date.**calendar** : Temporal.Calendar
+### date.**calendar** : object
The `calendar` read-only property gives the calendar that the `year`, `month`, and `day` properties are interpreted in.
@@ -269,10 +269,10 @@ date.with({day: 1}); // => 2006-01-01
date.plus({months: 1}).with({day: date.daysInMonth}); // => 2006-02-28
```
-### date.**withCalendar**(_calendar_: Temporal.Calendar | string) : Temporal.Date
+### date.**withCalendar**(_calendar_: object | string) : Temporal.Date
**Parameters:**
-- `calendar` (`Temporal.Calendar` or string): The calendar into which to project `date`.
+- `calendar` (`Temporal.Calendar` or plain object or string): The calendar into which to project `date`.
**Returns:** a new `Temporal.Date` object which is the date indicated by `date`, projected into `calendar`.
@@ -530,7 +530,7 @@ date.getYearMonth() // => 2006-08
date.getMonthDay() // => 08-24
```
-### date.**getFields**() : { year: number, month: number, day: number, calendar: Temporal.Calendar, [propName: string]: unknown }
+### date.**getFields**() : { year: number, month: number, day: number, calendar: object, [propName: string]: unknown }
**Returns:** a plain object with properties equal to the fields of `date`.
diff --git a/docs/datetime.md b/docs/datetime.md
index 798e39cdb2..82289021d3 100644
--- a/docs/datetime.md
+++ b/docs/datetime.md
@@ -21,7 +21,7 @@ A `Temporal.DateTime` can also be converted into any of the other `Temporal` obj
## Constructor
-### **new Temporal.DateTime**(_isoYear_: number, _isoMonth_: number, _isoDay_: number, _hour_: number = 0, _minute_: number = 0, _second_: number = 0, _millisecond_: number = 0, _microsecond_: number = 0, _nanosecond_: number = 0, _calendar_?: Temporal.Calendar) : Temporal.DateTime
+### **new Temporal.DateTime**(_isoYear_: number, _isoMonth_: number, _isoDay_: number, _hour_: number = 0, _minute_: number = 0, _second_: number = 0, _millisecond_: number = 0, _microsecond_: number = 0, _nanosecond_: number = 0, _calendar_?: object) : Temporal.DateTime
**Parameters:**
- `isoYear` (number): A year.
@@ -33,7 +33,7 @@ A `Temporal.DateTime` can also be converted into any of the other `Temporal` obj
- `millisecond` (optional number): A number of milliseconds, ranging between 0 and 999 inclusive.
- `microsecond` (optional number): A number of microseconds, ranging between 0 and 999 inclusive.
- `nanosecond` (optional number): A number of nanoseconds, ranging between 0 and 999 inclusive.
-- `calendar` (optional `Temporal.Calendar`): A calendar to project the date into.
+- `calendar` (optional `Temporal.Calendar` or plain object): A calendar to project the date into.
**Returns:** a new `Temporal.DateTime` object.
@@ -205,7 +205,7 @@ dt.microsecond // => 3
dt.nanosecond // => 500
```
-### datetime.**calendar** : Temporal.Calendar
+### datetime.**calendar** : object
The `calendar` read-only property gives the calendar that the `year`, `month`, and `day` properties are interpreted in.
@@ -326,10 +326,10 @@ dt = new Temporal.DateTime(1995, 12, 7, 3, 24, 30, 0, 3, 500);
dt.with({year: 2015, second: 31}) // => 2015-12-07T03:24:31.000003500
```
-### datetime.**withCalendar**(_calendar_: Temporal.Calendar | string) : Temporal.DateTime
+### datetime.**withCalendar**(_calendar_: object | string) : Temporal.DateTime
**Parameters:**
-- `calendar` (`Temporal.Calendar` or string): The calendar into which to project `datetime`.
+- `calendar` (`Temporal.Calendar` or plain object or string): The calendar into which to project `datetime`.
**Returns:** a new `Temporal.DateTime` object which is the date indicated by `datetime`, projected into `calendar`.
@@ -604,7 +604,7 @@ dt.getMonthDay() // => 12-07
dt.getTime() // => 03:24:30.000003500
```
-### datetime.**getFields**() : { year: number, month: number, day: number, hour: number, minute: number, second: number, millisecond: number, microsecond: number, nanosecond: number, calendar: Temporal.Calendar, [propName: string]: unknown }
+### datetime.**getFields**() : { year: number, month: number, day: number, hour: number, minute: number, second: number, millisecond: number, microsecond: number, nanosecond: number, calendar: object, [propName: string]: unknown }
**Returns:** a plain object with properties equal to the fields of `datetime`.
diff --git a/docs/monthday.md b/docs/monthday.md
index 7e0576ad66..eaa155ac4e 100644
--- a/docs/monthday.md
+++ b/docs/monthday.md
@@ -13,12 +13,12 @@ A `Temporal.MonthDay` can be converted into a `Temporal.Date` by combining it wi
## Constructor
-### **new Temporal.MonthDay**(_isoMonth_: number, _isoDay_: number, _calendar_?: Temporal.Calendar, _refISOYear_?: number) : Temporal.MonthDay
+### **new Temporal.MonthDay**(_isoMonth_: number, _isoDay_: number, _calendar_?: object, _refISOYear_?: number) : Temporal.MonthDay
**Parameters:**
- `isoMonth` (number): A month, ranging between 1 and 12 inclusive.
- `isoDay` (number): A day of the month, ranging between 1 and 31 inclusive.
-- `calendar` (optional `Temporal.Calendar`): A calendar to project the date into.
+- `calendar` (optional `Temporal.Calendar` or plain object): A calendar to project the date into.
- `refISOYear` (optional number): A reference year, used for disambiguation when implementing other calendar systems.
The default is the first leap year after the [Unix epoch](https://en.wikipedia.org/wiki/Unix_time).
You can omit this parameter unless using a non-ISO-8601 calendar.
@@ -112,7 +112,7 @@ md.month // => 8
md.day // => 24
```
-### monthDay.**calendar** : Temporal.Calendar
+### monthDay.**calendar** : object
The `calendar` read-only property gives the calendar that the `month` and `day` properties are interpreted in.
@@ -272,7 +272,7 @@ md.withYear(2020) // => 2020-02-29
In calendars where more information than just the year is needed to convert a `Temporal.MonthDay` to a `Temporal.Date`, you can pass an object to `withYear()` that contains the necessary properties.
-### monthDay.**getFields**() : { month: number, day: number, calendar: Temporal.Calendar, [propName: string]: unknown }
+### monthDay.**getFields**() : { month: number, day: number, calendar: object, [propName: string]: unknown }
**Returns:** a plain object with properties equal to the fields of `monthDay`.
diff --git a/docs/now.md b/docs/now.md
index 13bf364ecb..9cb8147c33 100644
--- a/docs/now.md
+++ b/docs/now.md
@@ -54,12 +54,12 @@ nextTransition.inTimeZone(tz);
// On 2020-03-08T03:00 the clock will change from UTC -08:00 to -07:00
```
-### Temporal.now.**dateTime**(_timeZone_: object | string = Temporal.now.timeZone(), _calendar_: Temporal.Calendar | string = 'iso8601') : Temporal.DateTime
+### Temporal.now.**dateTime**(_timeZone_: object | string = Temporal.now.timeZone(), _calendar_: object | string = 'iso8601') : Temporal.DateTime
**Parameters:**
- `timeZone` (optional object or string): The time zone to get the current date and time in, as a `Temporal.TimeZone` object, an object implementing the [time zone protocol](./timezone.md#protocol), or a string.
If not given, the current system time zone will be used.
-- `calendar` (optional `Temporal.Calendar` or string): The calendar system to get the current date and time in.
+- `calendar` (optional `Temporal.Calendar`, plain object, or string): The calendar system to get the current date and time in.
If not given, the ISO 8601 calendar will be used.
**Returns:** a `Temporal.DateTime` object representing the current system date and time.
@@ -85,12 +85,12 @@ Object.entries(financialCentres).forEach(([name, timeZone]) => {
// Tokyo: 2020-01-25T14:52:14.759534758
```
-### Temporal.now.**date**(_timeZone_: object | string = Temporal.now.timeZone(), _calendar_: Temporal.Calendar | string = 'iso8601') : Temporal.Date
+### Temporal.now.**date**(_timeZone_: object | string = Temporal.now.timeZone(), _calendar_: object | string = 'iso8601') : Temporal.Date
**Parameters:**
- `timeZone` (optional object or string): The time zone to get the current date and time in, as a `Temporal.TimeZone` object, an object implementing the [time zone protocol](./timezone.md#protocol), or a string.
If not given, the current system time zone will be used.
-- `calendar` (optional `Temporal.Calendar` or string): The calendar system to get the current date and time in.
+- `calendar` (optional `Temporal.Calendar`, plain object, or string): The calendar system to get the current date and time in.
If not given, the ISO 8601 calendar will be used.
**Returns:** a `Temporal.Date` object representing the current system date.
diff --git a/docs/timezone.md b/docs/timezone.md
index 80718cbbe4..d21644aeb4 100644
--- a/docs/timezone.md
+++ b/docs/timezone.md
@@ -173,11 +173,11 @@ tz = new Temporal.TimeZone('-08:00');
tz.getOffsetStringFor(timestamp); // => -08:00
```
-### timeZone.**getDateTimeFor**(_absolute_: Temporal.Absolute, _calendar_?: Temporal.Calendar | string) : Temporal.DateTime
+### timeZone.**getDateTimeFor**(_absolute_: Temporal.Absolute, _calendar_?: object | string) : Temporal.DateTime
**Parameters:**
- `absolute` (`Temporal.Absolute`): An absolute time to convert.
-- `calendar` (optional object or string): A `Temporal.Calendar` object, or a calendar identifier.
+- `calendar` (optional object or string): A `Temporal.Calendar` object, or a plain object, or a calendar identifier.
The default is to use the ISO 8601 calendar.
**Returns:** A `Temporal.DateTime` object indicating the calendar date and wall-clock time in `timeZone`, according to the reckoning of `calendar`, at the absolute time indicated by `absolute`.
diff --git a/docs/yearmonth.md b/docs/yearmonth.md
index 427d185486..76c8edd27c 100644
--- a/docs/yearmonth.md
+++ b/docs/yearmonth.md
@@ -13,12 +13,12 @@ A `Temporal.YearMonth` can be converted into a `Temporal.Date` by combining it w
## Constructor
-### **new Temporal.YearMonth**(_isoYear_: number, _isoMonth_: number, _calendar_?: Temporal.Calendar, _refISODay_: number = 1) : Temporal.YearMonth
+### **new Temporal.YearMonth**(_isoYear_: number, _isoMonth_: number, _calendar_?: object, _refISODay_: number = 1) : Temporal.YearMonth
**Parameters:**
- `isoYear` (number): A year.
- `isoMonth` (number): A month, ranging between 1 and 12 inclusive.
-- `calendar` (optional `Temporal.Calendar`): A calendar to project the month into.
+- `calendar` (optional `Temporal.Calendar` or plain object): A calendar to project the month into.
- `refISODay` (optional number): A reference day, used for disambiguation when implementing other calendar systems.
You can omit this parameter unless using a non-ISO-8601 calendar.
@@ -136,7 +136,7 @@ ym.year // => 2019
ym.month // => 6
```
-### yearMonth.**calendar** : Temporal.Calendar
+### yearMonth.**calendar** : object
The `calendar` read-only property gives the calendar that the `year` and `month` properties are interpreted in.
@@ -438,7 +438,7 @@ ym = Temporal.YearMonth.from('2019-06');
ym.withDay(24) // => 2019-06-24
```
-### yearMonth.**getFields**() : { year: number, month: number, calendar: Temporal.Calendar, [propName: string]: unknown }
+### yearMonth.**getFields**() : { year: number, month: number, calendar: object, [propName: string]: unknown }
**Returns:** a plain object with properties equal to the fields of `yearMonth`.
diff --git a/polyfill/index.d.ts b/polyfill/index.d.ts
index fa0655043f..db1cdc2acb 100644
--- a/polyfill/index.d.ts
+++ b/polyfill/index.d.ts
@@ -204,12 +204,53 @@ export namespace Temporal {
other: Temporal.Absolute,
options?: DifferenceOptions<'days' | 'hours' | 'minutes' | 'seconds'>
): Temporal.Duration;
- inTimeZone(tzLike?: TimeZoneProtocol | string, calendar?: Temporal.Calendar | string): Temporal.DateTime;
+ inTimeZone(tzLike?: TimeZoneProtocol | string, calendar?: CalendarProtocol | string): Temporal.DateTime;
toLocaleString(locales?: string | string[], options?: Intl.DateTimeFormatOptions): string;
toJSON(): string;
toString(tzLike?: TimeZoneProtocol | string): string;
}
+ type CalendarProtocol = {
+ id: string;
+ year?(date: Temporal.Date): number;
+ month?(date: Temporal.Date): number;
+ day?(date: Temporal.Date): number;
+ dayOfWeek?(date: Temporal.Date): number;
+ dayOfYear?(date: Temporal.Date): number;
+ weekOfYear?(date: Temporal.Date): number;
+ daysInMonth?(date: Temporal.Date): number;
+ daysInYear?(date: Temporal.Date): number;
+ isLeapYear?(date: Temporal.Date): boolean;
+ dateFromFields?(
+ fields: DateLike,
+ options: AssignmentOptions,
+ constructor: ConstructorOf
+ ): Temporal.Date;
+ yearMonthFromFields?(
+ fields: YearMonthLike,
+ options: AssignmentOptions,
+ constructor: ConstructorOf
+ ): Temporal.YearMonth;
+ monthDayFromFields?(
+ fields: MonthDayLike,
+ options: AssignmentOptions,
+ constructor: ConstructorOf
+ ): Temporal.MonthDay;
+ plus?(
+ date: Temporal.Date,
+ duration: Temporal.Duration,
+ options: ArithmeticOptions,
+ constructor: ConstructorOf
+ ): Temporal.Date;
+ minus?(
+ date: Temporal.Date,
+ duration: Temporal.Duration,
+ options: ArithmeticOptions,
+ constructor: ConstructorOf
+ ): Temporal.Date;
+ difference?(smaller: Temporal.Date, larger: Temporal.Date, options: DifferenceOptions);
+ };
+
/**
* A `Temporal.Calendar` is a representation of a calendar system. It includes
* information about how many days are in each year, how many months are in
@@ -218,7 +259,7 @@ export namespace Temporal {
*
* See https://tc39.es/proposal-temporal/docs/calendar.html for more details.
*/
- export class Calendar {
+ export class Calendar implements Required {
static from(item: Temporal.Calendar | string): Temporal.Calendar;
constructor(calendarIdentifier: string);
readonly id: string;
@@ -266,14 +307,14 @@ export namespace Temporal {
year?: number;
month?: number;
day?: number;
- calendar?: string | Temporal.Calendar;
+ calendar?: string | CalendarProtocol;
};
type DateFields = {
year: number;
month: number;
day: number;
- calendar: Temporal.Calendar;
+ calendar: CalendarProtocol;
};
type DateISOCalendarFields = {
@@ -294,11 +335,11 @@ export namespace Temporal {
export class Date implements Required {
static from(item: Temporal.Date | DateLike | string, options?: AssignmentOptions): Temporal.Date;
static compare(one: Temporal.Date, two: Temporal.Date): ComparisonResult;
- constructor(isoYear: number, isoMonth: number, isoDay: number, calendar?: Temporal.Calendar);
+ constructor(isoYear: number, isoMonth: number, isoDay: number, calendar?: CalendarProtocol);
readonly year: number;
readonly month: number;
readonly day: number;
- readonly calendar: Temporal.Calendar;
+ readonly calendar: CalendarProtocol;
readonly dayOfWeek: number;
readonly dayOfYear: number;
readonly weekOfYear: number;
@@ -307,7 +348,7 @@ export namespace Temporal {
readonly isLeapYear: boolean;
equals(other: Temporal.Date): boolean;
with(dateLike: DateLike, options?: AssignmentOptions): Temporal.Date;
- withCalendar(calendar: string | Temporal.Calendar): Temporal.Date;
+ withCalendar(calendar: string | CalendarProtocol): Temporal.Date;
plus(durationLike: Temporal.Duration | DurationLike, options?: ArithmeticOptions): Temporal.Date;
minus(durationLike: Temporal.Duration | DurationLike, options?: ArithmeticOptions): Temporal.Date;
difference(
@@ -334,7 +375,7 @@ export namespace Temporal {
millisecond?: number;
microsecond?: number;
nanosecond?: number;
- calendar?: string | Temporal.Calendar;
+ calendar?: string | CalendarProtocol;
};
type DateTimeFields = {
@@ -347,7 +388,7 @@ export namespace Temporal {
millisecond: number;
microsecond: number;
nanosecond: number;
- calendar: Temporal.Calendar;
+ calendar: CalendarProtocol;
};
type DateTimeISOCalendarFields = {
@@ -385,7 +426,7 @@ export namespace Temporal {
millisecond?: number,
microsecond?: number,
nanosecond?: number,
- calendar?: Temporal.Calendar
+ calendar?: CalendarProtocol
);
readonly year: number;
readonly month: number;
@@ -396,7 +437,7 @@ export namespace Temporal {
readonly millisecond: number;
readonly microsecond: number;
readonly nanosecond: number;
- readonly calendar: Temporal.Calendar;
+ readonly calendar: CalendarProtocol;
readonly dayOfWeek: number;
readonly dayOfYear: number;
readonly weekOfYear: number;
@@ -405,7 +446,7 @@ export namespace Temporal {
readonly isLeapYear: boolean;
equals(other: Temporal.DateTime): boolean;
with(dateTimeLike: DateTimeLike, options?: AssignmentOptions): Temporal.DateTime;
- withCalendar(calendar: string | Temporal.Calendar): Temporal.DateTime;
+ withCalendar(calendar: string | CalendarProtocol): Temporal.DateTime;
plus(durationLike: Temporal.Duration | DurationLike, options?: ArithmeticOptions): Temporal.DateTime;
minus(durationLike: Temporal.Duration | DurationLike, options?: ArithmeticOptions): Temporal.DateTime;
difference(
@@ -432,7 +473,7 @@ export namespace Temporal {
type MonthDayFields = {
month: number;
day: number;
- calendar: Temporal.Calendar;
+ calendar: CalendarProtocol;
};
/**
@@ -444,10 +485,10 @@ export namespace Temporal {
*/
export class MonthDay implements Required {
static from(item: Temporal.MonthDay | MonthDayLike | string, options?: AssignmentOptions): Temporal.MonthDay;
- constructor(isoMonth: number, isoDay: number, calendar?: Temporal.Calendar, refISOYear?: number);
+ constructor(isoMonth: number, isoDay: number, calendar?: CalendarProtocol, refISOYear?: number);
readonly month: number;
readonly day: number;
- readonly calendar: Temporal.Calendar;
+ readonly calendar: CalendarProtocol;
equals(other: Temporal.MonthDay): boolean;
with(monthDayLike: MonthDayLike, options?: AssignmentOptions): Temporal.MonthDay;
withYear(year: number | { year: number }): Temporal.Date;
@@ -545,7 +586,7 @@ export namespace Temporal {
readonly name: string;
getOffsetNanosecondsFor(absolute: Temporal.Absolute): number;
getOffsetStringFor(absolute: Temporal.Absolute): string;
- getDateTimeFor(absolute: Temporal.Absolute, calendar?: Temporal.Calendar | string): Temporal.DateTime;
+ getDateTimeFor(absolute: Temporal.Absolute, calendar?: CalendarProtocol | string): Temporal.DateTime;
getAbsoluteFor(dateTime: Temporal.DateTime, options?: ToAbsoluteOptions): Temporal.Absolute;
getTransitions(startingPoint: Temporal.Absolute): IteratorResult;
getPossibleAbsolutesFor(dateTime: Temporal.DateTime): Temporal.Absolute[];
@@ -561,7 +602,7 @@ export namespace Temporal {
type YearMonthFields = {
year: number;
month: number;
- calendar: Temporal.Calendar;
+ calendar: CalendarProtocol;
};
/**
@@ -574,10 +615,10 @@ export namespace Temporal {
export class YearMonth implements Required {
static from(item: Temporal.YearMonth | YearMonthLike | string, options?: AssignmentOptions): Temporal.YearMonth;
static compare(one: Temporal.YearMonth, two: Temporal.YearMonth): ComparisonResult;
- constructor(isoYear: number, isoMonth: number, calendar?: Temporal.Calendar, refISODay?: number);
+ constructor(isoYear: number, isoMonth: number, calendar?: CalendarProtocol, refISODay?: number);
readonly year: number;
readonly month: number;
- readonly calendar: Temporal.Calendar;
+ readonly calendar: CalendarProtocol;
readonly daysInMonth: number;
readonly daysInYear: number;
readonly isLeapYear: boolean;
@@ -619,12 +660,13 @@ export namespace Temporal {
* string (e.g. `'Europe/London'`), `Temporal.TimeZone` instance, or an
* object implementing the time zone protocol. If omitted,
* the environment's current time zone will be used.
- * @param {Temporal.Calendar | string} [calendar] - calendar identifier or a
- * `Temporal.Calendar` instance. If omitted, the ISO 8601 calendar is used.
+ * @param {Temporal.Calendar | string} [calendar] - calendar identifier, or
+ * a `Temporal.Calendar` instance, or an object implementing the calendar
+ * protocol. If omitted, the ISO 8601 calendar is used.
*/
export function dateTime(
tzLike?: TimeZoneProtocol | string,
- calendar?: Temporal.Calendar | string
+ calendar?: CalendarProtocol | string
): Temporal.DateTime;
/**
@@ -635,10 +677,11 @@ export namespace Temporal {
* string (e.g. `'Europe/London'`), `Temporal.TimeZone` instance, or an
* object implementing the time zone protocol. If omitted,
* the environment's current time zone will be used.
- * @param {Temporal.Calendar | string} [calendar] - calendar identifier or a
- * `Temporal.Calendar` instance. If omitted, the ISO 8601 calendar is used.
+ * @param {Temporal.Calendar | string} [calendar] - calendar identifier, or
+ * a `Temporal.Calendar` instance, or an object implementing the calendar
+ * protocol. If omitted, the ISO 8601 calendar is used.
*/
- export function date(tzLike?: TimeZoneProtocol | string, calendar?: Temporal.Calendar | string): Temporal.Date;
+ export function date(tzLike?: TimeZoneProtocol | string, calendar?: CalendarProtocol | string): Temporal.Date;
/**
* Get the current clock time in a specific time zone.
diff --git a/polyfill/lib/calendar.mjs b/polyfill/lib/calendar.mjs
index e258fba41c..1fe5655f44 100644
--- a/polyfill/lib/calendar.mjs
+++ b/polyfill/lib/calendar.mjs
@@ -90,7 +90,7 @@ export class Calendar {
return GetSlot(this, CALENDAR_ID);
}
static from(item) {
- if (ES.IsTemporalCalendar(item)) return item;
+ if (ES.IsTemporalCalendar(item) || (typeof item === 'object' && item)) return item;
const stringIdent = ES.ToString(item);
return ES.GetBuiltinCalendar(stringIdent);
}
diff --git a/polyfill/test/Date/constructor/from/order-of-operations.js b/polyfill/test/Date/constructor/from/order-of-operations.js
index c843d85c66..bf5e6a61c9 100644
--- a/polyfill/test/Date/constructor/from/order-of-operations.js
+++ b/polyfill/test/Date/constructor/from/order-of-operations.js
@@ -8,7 +8,6 @@ includes: [compareArray.js]
const expected = [
"get calendar",
- "toString calendar",
"get day",
"valueOf day",
"get month",
@@ -21,11 +20,11 @@ const fields = {
year: 1.7,
month: 1.7,
day: 1.7,
- calendar: "iso8601",
};
const argument = new Proxy(fields, {
get(target, key) {
actual.push(`get ${key}`);
+ if (key === "calendar") return Temporal.Calendar.from('iso8601');
const result = target[key];
return {
valueOf() {
diff --git a/polyfill/test/Date/prototype/with/order-of-operations.js b/polyfill/test/Date/prototype/with/order-of-operations.js
index 2650c66414..75604790d0 100644
--- a/polyfill/test/Date/prototype/with/order-of-operations.js
+++ b/polyfill/test/Date/prototype/with/order-of-operations.js
@@ -9,7 +9,6 @@ includes: [compareArray.js]
const instance = new Temporal.Date(2000, 5, 2);
const expected = [
"get calendar",
- "toString calendar",
"get day",
"valueOf day",
"get month",
@@ -22,11 +21,11 @@ const fields = {
year: 1.7,
month: 1.7,
day: 1.7,
- calendar: "iso8601",
};
const argument = new Proxy(fields, {
get(target, key) {
actual.push(`get ${key}`);
+ if (key === "calendar") return Temporal.Calendar.from('iso8601');
const result = target[key];
if (result === undefined) {
return undefined;
diff --git a/polyfill/test/DateTime/constructor/from/order-of-operations.js b/polyfill/test/DateTime/constructor/from/order-of-operations.js
index 129da83eae..184de241a1 100644
--- a/polyfill/test/DateTime/constructor/from/order-of-operations.js
+++ b/polyfill/test/DateTime/constructor/from/order-of-operations.js
@@ -8,7 +8,6 @@ includes: [compareArray.js]
const expected = [
"get calendar",
- "toString calendar",
"get day",
"valueOf day",
"get hour",
@@ -39,11 +38,11 @@ const fields = {
millisecond: 1.7,
microsecond: 1.7,
nanosecond: 1.7,
- calendar: "iso8601",
};
const argument = new Proxy(fields, {
get(target, key) {
actual.push(`get ${key}`);
+ if (key === "calendar") return Temporal.Calendar.from('iso8601');
const result = target[key];
return {
valueOf() {
diff --git a/polyfill/test/DateTime/prototype/with/order-of-operations.js b/polyfill/test/DateTime/prototype/with/order-of-operations.js
index 36c40c6235..a417538d1d 100644
--- a/polyfill/test/DateTime/prototype/with/order-of-operations.js
+++ b/polyfill/test/DateTime/prototype/with/order-of-operations.js
@@ -9,7 +9,6 @@ includes: [compareArray.js]
const instance = new Temporal.DateTime(2000, 5, 2, 12, 34, 56, 987, 654, 321);
const expected = [
"get calendar",
- "toString calendar",
"get day",
"valueOf day",
"get hour",
@@ -40,11 +39,11 @@ const fields = {
millisecond: 1.7,
microsecond: 1.7,
nanosecond: 1.7,
- calendar: "iso8601",
};
const argument = new Proxy(fields, {
get(target, key) {
actual.push(`get ${key}`);
+ if (key === "calendar") return Temporal.Calendar.from('iso8601');
const result = target[key];
if (result === undefined) {
return undefined;
diff --git a/polyfill/test/MonthDay/constructor/from/order-of-operations.js b/polyfill/test/MonthDay/constructor/from/order-of-operations.js
index 990af90ec2..8b0e0a4e33 100644
--- a/polyfill/test/MonthDay/constructor/from/order-of-operations.js
+++ b/polyfill/test/MonthDay/constructor/from/order-of-operations.js
@@ -8,7 +8,6 @@ includes: [compareArray.js]
const expected = [
"get calendar",
- "toString calendar",
"get day",
"valueOf day",
"get month",
@@ -18,11 +17,11 @@ const actual = [];
const fields = {
month: 1.7,
day: 1.7,
- calendar: "iso8601",
};
const argument = new Proxy(fields, {
get(target, key) {
actual.push(`get ${key}`);
+ if (key === "calendar") return Temporal.Calendar.from('iso8601');
const result = target[key];
return {
valueOf() {
diff --git a/polyfill/test/YearMonth/constructor/from/order-of-operations.js b/polyfill/test/YearMonth/constructor/from/order-of-operations.js
index 08c6e01f38..0017355d0f 100644
--- a/polyfill/test/YearMonth/constructor/from/order-of-operations.js
+++ b/polyfill/test/YearMonth/constructor/from/order-of-operations.js
@@ -8,7 +8,6 @@ includes: [compareArray.js]
const expected = [
"get calendar",
- "toString calendar",
"get month",
"valueOf month",
"get year",
@@ -18,11 +17,11 @@ const actual = [];
const fields = {
year: 1.7,
month: 1.7,
- calendar: "iso8601",
};
const argument = new Proxy(fields, {
get(target, key) {
actual.push(`get ${key}`);
+ if (key === "calendar") return Temporal.Calendar.from('iso8601');
const result = target[key];
return {
valueOf() {
diff --git a/polyfill/test/usercalendar.mjs b/polyfill/test/usercalendar.mjs
index 40874555db..3e8b082598 100644
--- a/polyfill/test/usercalendar.mjs
+++ b/polyfill/test/usercalendar.mjs
@@ -222,6 +222,243 @@ describe('Userland calendar', () => {
});
});
});
+ describe('Trivial protocol implementation', () => {
+ // For the purposes of testing, a nonsensical calendar that has 10-month
+ // years and 10-day months, and the year zero is at the Unix epoch
+ function decimalToISO(year, month, day, disambiguation) {
+ if (disambiguation === 'constrain') {
+ if (month < 1) month = 1;
+ if (month > 10) month = 10;
+ if (day < 1) day = 1;
+ if (day > 10) day = 10;
+ } else if (disambiguation === 'reject') {
+ if (month < 1 || month > 10 || day < 1 || day > 10) {
+ throw new RangeError('invalid value');
+ }
+ }
+ const days = year * 100 + (month - 1) * 10 + (day - 1);
+ return new Temporal.Date(1970, 1, 1).plus({ days });
+ }
+ function isoToDecimal(date) {
+ const iso = date.getISOCalendarFields();
+ const isoDate = new Temporal.Date(iso.year, iso.month, iso.day);
+ let { days } = isoDate.difference(new Temporal.Date(1970, 1, 1), { largestUnit: 'days' });
+ let year = Math.floor(days / 100);
+ if (iso.year < 1970) year *= -1;
+ days %= 100;
+ return { year, days };
+ }
+ const obj = {
+ id: 'decimal',
+ dateFromFields(fields, options, constructor) {
+ const { disambiguation = 'constrain' } = options ? options : {};
+ const isoDate = decimalToISO(fields.year, fields.month, fields.day, disambiguation);
+ return new constructor(isoDate.year, isoDate.month, isoDate.day, this);
+ },
+ yearMonthFromFields(fields, options, constructor) {
+ const { disambiguation = 'constrain' } = options ? options : {};
+ const isoDate = decimalToISO(fields.year, fields.month, 1, disambiguation);
+ return new constructor(isoDate.year, isoDate.month, this, isoDate.day);
+ },
+ monthDayFromFields(fields, options, constructor) {
+ const { disambiguation = 'constrain' } = options ? options : {};
+ const isoDate = decimalToISO(0, fields.month, fields.day, disambiguation);
+ return new constructor(isoDate.month, isoDate.day, this, isoDate.year);
+ },
+ year(date) {
+ return isoToDecimal(date).year;
+ },
+ month(date) {
+ const { days } = isoToDecimal(date);
+ return Math.floor(days / 10) + 1;
+ },
+ day(date) {
+ const { days } = isoToDecimal(date);
+ return (days % 10) + 1;
+ }
+ };
+
+ const date = Temporal.Date.from({ year: 184, month: 2, day: 9, calendar: obj });
+ const dt = Temporal.DateTime.from({ year: 184, month: 2, day: 9, hour: 12, calendar: obj });
+ const ym = Temporal.YearMonth.from({ year: 184, month: 2, calendar: obj });
+ const md = Temporal.MonthDay.from({ month: 2, day: 9, calendar: obj });
+
+ it('is a calendar', () => equal(typeof obj, 'object'));
+ it('.id property', () => equal(obj.id, 'decimal'));
+ // FIXME: what should happen in Temporal.Calendar.from(obj)?
+ it('.id is not available in from()', () => {
+ throws(() => Temporal.Calendar.from('decimal'), RangeError);
+ throws(() => Temporal.Calendar.from('2020-06-05T09:34-07:00[America/Vancouver][c=decimal]'), RangeError);
+ });
+ it('Temporal.Date.from()', () => equal(`${date}`, '2020-06-05[c=decimal]'));
+ it('Temporal.Date fields', () => {
+ equal(date.year, 184);
+ equal(date.month, 2);
+ equal(date.day, 9);
+ });
+ it('date.with()', () => {
+ const date2 = date.with({ year: 0 });
+ equal(date2.year, 0);
+ });
+ it('date.withCalendar()', () => {
+ const date2 = Temporal.Date.from('2020-06-05T12:00');
+ assert(date2.withCalendar(obj).equals(date));
+ });
+ it('Temporal.DateTime.from()', () => equal(`${dt}`, '2020-06-05T12:00[c=decimal]'));
+ it('Temporal.DateTime fields', () => {
+ equal(dt.year, 184);
+ equal(dt.month, 2);
+ equal(dt.day, 9);
+ equal(dt.hour, 12);
+ equal(dt.minute, 0);
+ equal(dt.second, 0);
+ equal(dt.millisecond, 0);
+ equal(dt.microsecond, 0);
+ equal(dt.nanosecond, 0);
+ });
+ it('datetime.with()', () => {
+ const dt2 = dt.with({ year: 0 });
+ equal(dt2.year, 0);
+ });
+ it('datetime.withCalendar()', () => {
+ const dt2 = Temporal.DateTime.from('2020-06-05T12:00');
+ assert(dt2.withCalendar(obj).equals(dt));
+ });
+ it('Temporal.YearMonth.from()', () => equal(`${ym}`, '2020-05-28[c=decimal]'));
+ it('Temporal.YearMonth fields', () => {
+ equal(dt.year, 184);
+ equal(dt.month, 2);
+ });
+ it('yearmonth.with()', () => {
+ const ym2 = ym.with({ year: 0 });
+ equal(ym2.year, 0);
+ });
+ it('Temporal.MonthDay.from()', () => equal(`${md}`, '1970-01-19[c=decimal]'));
+ it('Temporal.MonthDay fields', () => {
+ equal(dt.month, 2);
+ equal(dt.day, 9);
+ });
+ it('monthday.with()', () => {
+ const md2 = md.with({ month: 1 });
+ equal(md2.month, 1);
+ });
+ it('timezone.getDateTimeFor()', () => {
+ const tz = Temporal.TimeZone.from('UTC');
+ const abs = Temporal.Absolute.fromEpochSeconds(0);
+ const dt = tz.getDateTimeFor(abs, obj);
+ equal(dt.calendar.id, obj.id);
+ });
+ it('absolute.inTimeZone()', () => {
+ const abs = Temporal.Absolute.fromEpochSeconds(0);
+ const dt = abs.inTimeZone('UTC', obj);
+ equal(dt.calendar.id, obj.id);
+ });
+ it('Temporal.now.dateTime()', () => {
+ const nowDateTime = Temporal.now.dateTime('UTC', obj);
+ equal(nowDateTime.calendar.id, obj.id);
+ });
+ it('Temporal.now.date()', () => {
+ const nowDate = Temporal.now.date('UTC', obj);
+ equal(nowDate.calendar.id, obj.id);
+ });
+ describe('Making available globally', () => {
+ const originalTemporalCalendarFrom = Temporal.Calendar.from;
+ before(() => {
+ Temporal.Calendar.from = function(item) {
+ let id;
+ if (item instanceof Temporal.Calendar) {
+ id = item.id;
+ } else {
+ id = `${item}`;
+ // TODO: Use Temporal.parse here to extract the ID from an ISO string
+ }
+ if (id === 'decimal') return obj;
+ return originalTemporalCalendarFrom.call(this, id);
+ };
+ });
+ it('works for Calendar.from(id)', () => {
+ const cal = Temporal.Calendar.from('decimal');
+ assert(Object.is(cal, obj));
+ });
+ const iso = '1970-01-01T00:00+00:00[c=decimal]';
+ it.skip('works for Calendar.from(ISO string)', () => {
+ const cal = Temporal.Calendar.from(iso);
+ assert(Object.is(cal, obj));
+ });
+ it('works for Date.from(iso)', () => {
+ const d = Temporal.Date.from(iso);
+ equal(`${d}`, '1970-01-01[c=decimal]');
+ });
+ it('works for Date.from(props)', () => {
+ const d = Temporal.Date.from({ year: 0, month: 1, day: 1, calendar: 'decimal' });
+ equal(`${d}`, '1970-01-01[c=decimal]');
+ });
+ it('works for Date.with', () => {
+ const d1 = Temporal.Date.from('1970-01-01');
+ const d2 = d1.with({ month: 2, calendar: 'decimal' });
+ equal(`${d2}`, '1970-01-11[c=decimal]');
+ });
+ it('works for Date.withCalendar', () => {
+ const d = Temporal.Date.from('1970-01-01');
+ assert(d.withCalendar('decimal').equals(Temporal.Date.from(iso)));
+ });
+ it('works for DateTime.from(iso)', () => {
+ const dt = Temporal.DateTime.from(iso);
+ equal(`${dt}`, '1970-01-01T00:00[c=decimal]');
+ });
+ it('works for DateTime.from(props)', () => {
+ const dt = Temporal.DateTime.from({ year: 0, month: 1, day: 1, hour: 12, calendar: 'decimal' });
+ equal(`${dt}`, '1970-01-01T12:00[c=decimal]');
+ });
+ it('works for DateTime.with', () => {
+ const dt1 = Temporal.DateTime.from('1970-01-01T12:00');
+ const dt2 = dt1.with({ month: 2, calendar: 'decimal' });
+ equal(`${dt2}`, '1970-01-11T12:00[c=decimal]');
+ });
+ it('works for DateTime.withCalendar', () => {
+ const dt = Temporal.DateTime.from('1970-01-01T00:00');
+ assert(dt.withCalendar('decimal').equals(Temporal.DateTime.from(iso)));
+ });
+ it('works for YearMonth.from(iso)', () => {
+ const ym = Temporal.YearMonth.from(iso);
+ equal(`${ym}`, '1970-01-01[c=decimal]');
+ });
+ it('works for YearMonth.from(props)', () => {
+ const ym = Temporal.YearMonth.from({ year: 0, month: 1, calendar: 'decimal' });
+ equal(`${ym}`, '1970-01-01[c=decimal]');
+ });
+ it('works for MonthDay.from(iso)', () => {
+ const md = Temporal.MonthDay.from(iso);
+ equal(`${md}`, '1970-01-01[c=decimal]');
+ });
+ it('works for MonthDay.from(props)', () => {
+ const md = Temporal.MonthDay.from({ month: 1, day: 1, calendar: 'decimal' });
+ equal(`${md}`, '1970-01-01[c=decimal]');
+ });
+ it('works for TimeZone.getDateTimeFor', () => {
+ const tz = Temporal.TimeZone.from('UTC');
+ const abs = Temporal.Absolute.fromEpochSeconds(0);
+ const dt = tz.getDateTimeFor(abs, 'decimal');
+ equal(dt.calendar.id, 'decimal');
+ });
+ it('works for Absolute.inTimeZone', () => {
+ const abs = Temporal.Absolute.fromEpochSeconds(0);
+ const dt = abs.inTimeZone('UTC', 'decimal');
+ equal(dt.calendar.id, 'decimal');
+ });
+ it('works for Temporal.now.dateTime', () => {
+ const nowDateTime = Temporal.now.dateTime('UTC', 'decimal');
+ equal(nowDateTime.calendar.id, 'decimal');
+ });
+ it('works for Temporal.now.date', () => {
+ const nowDate = Temporal.now.date('UTC', 'decimal');
+ equal(nowDate.calendar.id, 'decimal');
+ });
+ after(() => {
+ Temporal.Calendar.from = originalTemporalCalendarFrom;
+ });
+ });
+ });
});
import { normalize } from 'path';