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';