diff --git a/docs/date.md b/docs/date.md index 7ed7b56e16..e6e5c3461f 100644 --- a/docs/date.md +++ b/docs/date.md @@ -16,20 +16,21 @@ A `Temporal.Date` can be converted into a `Temporal.DateTime` by combining it wi ## Constructor -### **new Temporal.Date**(_isoYear_: number, _isoMonth_: number, _isoDay_: number) : Temporal.Date +### **new Temporal.Date**(_isoYear_: number, _isoMonth_: number, _isoDay_: number, _calendar_?: Temporal.Calendar) : 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. **Returns:** a new `Temporal.Date` object. -Use this constructor if you have the correct parameters for the date already as individual number values. -Otherwise, `Temporal.Date.from()`, which accepts more kinds of input and allows disambiguation behaviour, is probably more convenient. +Use this constructor if you have the correct parameters for the date already as individual number values in the ISO 8601 calendar. +Otherwise, `Temporal.Date.from()`, which accepts more kinds of input, allows inputting dates in different calendar reckonings, and allows disambiguation behaviour, is probably more convenient. All values are given as reckoned in the [ISO 8601 calendar](https://en.wikipedia.org/wiki/ISO_8601#Dates). -Together, `isoYear`, `isoMonth`, and `isoDay` must represent a valid date in that calendar. +Together, `isoYear`, `isoMonth`, and `isoDay` must represent a valid date in that calendar, even if you are passing a different calendar as the `calendar` parameter. The range of allowed values for this type is exactly enough that calling [`getDate()`](./datetime.html#getDate) on any valid `Temporal.DateTime` will succeed. If `isoYear`, `isoMonth`, and `isoDay` form a date outside of this range, then `constrain` mode will clamp the values to the limit of the allowed range, while `reject` mode will throw a `RangeError`. @@ -58,7 +59,8 @@ date = new Temporal.Date(2020, 3, 14) // => 2020-03-14 This static method creates a new `Temporal.Date` object from another value. If the value is another `Temporal.Date` object, a new object representing the same date is returned. -If the value is any other object, it must have `year`, `month`, and `day` properties, and a `Temporal.Date` will be constructed from them. +If the value is any other object, it must have `year`, `month`, and `day` properties, and optionally a `calendar` property. +A `Temporal.Date` will be constructed from these properties. Any non-object value is converted to a string, which is expected to be in ISO 8601 format. Any time or time zone part is optional and will be ignored. @@ -83,6 +85,11 @@ date = Temporal.Date.from({year: 2006, month: 8, day: 24}); // => 2006-08-24 date = Temporal.Date.from(Temporal.DateTime.from('2006-08-24T15:43:27')); // => same as above; Temporal.DateTime has year, month, and day properties +calendar = Temporal.Calendar.from('islamic'); +date = Temporal.Date.from({ year: 1427, month; 8, day: 1, calendar }); // => 2006-08-24[c=islamic] +date = Temporal.Date.from({ year: 1427, month: 8, day: 1, calendar: 'islamic' }); + // => same as above + // Different disambiguation modes date = Temporal.Date.from({ year: 2001, month: 13, day: 1 }, { disambiguation: 'constrain' }) // => 2001-12-01 @@ -138,10 +145,14 @@ date.month // => 8 date.day // => 24 ``` +### date.**calendar** : Temporal.Calendar + +The `calendar` read-only property gives the calendar that the `year`, `month`, and `day` properties are interpreted in. + ### date.**dayOfWeek** : number The `dayOfWeek` read-only property gives the weekday number that the date falls on. -The weekday number is defined as in the ISO 8601 standard: a value between 1 and 7, inclusive, with Monday being 1, and Sunday 7. +For the ISO 8601 calendar, the weekday number is defined as in the ISO 8601 standard: a value between 1 and 7, inclusive, with Monday being 1, and Sunday 7. For an overview, see [ISO 8601 on Wikipedia](https://en.wikipedia.org/wiki/ISO_8601#Week_dates). Usage example: @@ -153,7 +164,7 @@ date = Temporal.Date.from('2006-08-24'); ### date.**dayOfYear** : number The `dayOfYear` read-only property gives the ordinal day of the year that the date falls on. -This is a value between 1 and 365, or 366 in a leap year. +For the ISO 8601 calendar, this is a value between 1 and 365, or 366 in a leap year. Usage example: ```javascript @@ -165,7 +176,7 @@ console.log(date.year, date.dayOfYear); // 2006 236 ### date.**weekOfYear** : number The `weekOfYear` read-only property gives the ISO week number of the date. -This is normally a value between 1 and 52, but in a few cases it can be 53 as well. +For the ISO 8601 calendar, this is normally a value between 1 and 52, but in a few cases it can be 53 as well. ISO week 1 is the week containing the first Thursday of the year. For more information on ISO week numbers, see for example the Wikipedia article on [ISO week date](https://en.wikipedia.org/wiki/ISO_week_date). @@ -179,7 +190,7 @@ console.log(date.year, date.weekOfYear, date.dayOfWeek); // 2006 34 4 ### date.**daysInMonth** : number The `daysInMonth` read-only property gives the number of days in the month that the date falls in. -This is 28, 29, 30, or 31, depending on the month and whether the year is a leap year. +For the ISO 8601 calendar, this is 28, 29, 30, or 31, depending on the month and whether the year is a leap year. Usage example: ```javascript @@ -202,7 +213,7 @@ console.log(poem); ### date.**daysInYear** : number The `daysInYear` read-only property gives the number of days in the year that the date falls in. -This is 365 or 366, depending on whether the year is a leap year. +For the ISO 8601 calendar, this is 365 or 366, depending on whether the year is a leap year. Usage example: ```javascript @@ -246,6 +257,9 @@ Since `Temporal.Date` objects are immutable, use this method instead of modifyin > **NOTE**: The allowed values for the `dateLike.month` property start at 1, which is different from legacy `Date` where months are represented by zero-based indices (0 to 11). +> **NOTE**: If a `calendar` property is provided on `dateLike`, the new calendar is applied first, before any of the other properties. +> If you are passing in an object with _only_ a `calendar` property, it is recommended to use the `withCalendar` method instead. + Usage example: ```javascript date = Temporal.Date.from('2006-01-24'); @@ -501,7 +515,7 @@ date.getYearMonth() // => 2006-08 date.getMonthDay() // => 08-24 ``` -### date.**getFields**() : { year: number, month: number, day: number, [propName: string]: unknown } +### date.**getFields**() : { year: number, month: number, day: number, calendar: Temporal.Calendar, [propName: string]: unknown } **Returns:** a plain object with properties equal to the fields of `date`. @@ -525,10 +539,18 @@ Object.assign({}, date.getFields()).day // => 24 This method is mainly useful if you are implementing a custom calendar. Most code will not need to use it. -Use `date.getFields()` instead. +Use `date.getFields()` instead, or `date.withCalendar('iso8601').getFields()`. Usage example: ```javascript date = Temporal.Date.from('2006-08-24'); date.getISOCalendarFields().day // => 24 + +// Date in other calendar +date = date.withCalendar('hebrew'); +date.getFields().day // => 30 +date.getISOCalendarFields().day // => 24 + +// Most likely what you need is this: +date.withCalendar('iso8601').day // => 24 ``` diff --git a/docs/datetime.md b/docs/datetime.md index 518c3446f0..bd167e1065 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) : 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_?: Temporal.Calendar) : Temporal.DateTime **Parameters:** - `isoYear` (number): A year. @@ -33,14 +33,15 @@ 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. **Returns:** a new `Temporal.DateTime` object. -Use this constructor if you have the correct parameters for the date already as individual number values. -Otherwise, `Temporal.DateTime.from()`, which accepts more kinds of input and allows disambiguation behaviour, is probably more convenient. +Use this constructor if you have the correct parameters for the date already as individual number values in the ISO 8601 calendar. +Otherwise, `Temporal.DateTime.from()`, which accepts more kinds of input, allows inputting dates in different calendar reckonings, and allows disambiguation behaviour, is probably more convenient. All values are given as reckoned in the [ISO 8601 calendar](https://en.wikipedia.org/wiki/ISO_8601#Dates). -Together, `isoYear`, `isoMonth`, and `isoDay` must represent a valid date in that calendar, and the time parameters must represent a valid time of day. +Together, `isoYear`, `isoMonth`, and `isoDay` must represent a valid date in that calendar, even if you are passing a different calendar as the `calendar` parameter, and the time parameters must represent a valid time of day. > **NOTE**: Although Temporal does not deal with leap seconds, dates coming from other software may have a `second` value of 60. > This value will cause the constructor will throw, so if you have to interoperate with times that may contain leap seconds, use `Temporal.DateTime.from()` instead. @@ -72,8 +73,9 @@ datetime = new Temporal.DateTime(2020, 3, 14, 13, 37) // => 2020-03-14T13:37 This static method creates a new `Temporal.DateTime` object from another value. If the value is another `Temporal.DateTime` object, a new object representing the same date and time is returned. -If the value is any other object, a `Temporal.DateTime` will be constructed from the values of any `year`, `month`, `day`, `hour`, `minute`, `second`, `millisecond`, `microsecond`, and `nanosecond` properties that are present. +If the value is any other object, a `Temporal.DateTime` will be constructed from the values of any `year`, `month`, `day`, `hour`, `minute`, `second`, `millisecond`, `microsecond`, `nanosecond`, and `calendar` properties that are present. At least the `year`, `month`, and `day` properties must be present. +If `calendar` is missing, it will be assumed to be `Temporal.Calendar.from('iso8601')`. Any other missing ones will be assumed to be 0. Any non-object value is converted to a string, which is expected to be in ISO 8601 format. @@ -113,6 +115,12 @@ dt = Temporal.DateTime.from({year: 1995, month: 12, day: 7}); // => 1995-12-07T dt = Temporal.DateTime.from(Temporal.Date.from('1995-12-07T03:24:30')); // => same as above; Temporal.Date has year, month, and day properties +calendar = Temporal.Calendar.from('hebrew'); +dt = Temporal.DateTime.from({ year: 5756, month: 3, day: 14, hour: 3, minute: 24, second: 30, calendar }); + // => 1995-12-07T03:24:30[c=hebrew] +dt = Temporal.DateTime.from({ year: 5756, month: 3, day: 14, hour: 3, minute: 24, second: 30, calendar: 'hebrew' }); + // => same as above + // Different disambiguation modes dt = Temporal.DateTime.from({ year: 2001, month: 13, day: 1 }, { disambiguation: 'constrain' }) // => 2001-12-01T00:00 @@ -197,10 +205,14 @@ dt.microsecond // => 3 dt.nanosecond // => 500 ``` +### datetime.**calendar** : Temporal.Calendar + +The `calendar` read-only property gives the calendar that the `year`, `month`, and `day` properties are interpreted in. + ### datetime.**dayOfWeek** : number The `dayOfWeek` read-only property gives the weekday number that the date falls on. -The weekday number is defined as in the ISO 8601 standard: a value between 1 and 7, inclusive, with Monday being 1, and Sunday 7. +For the ISO 8601 calendar, the weekday number is defined as in the ISO 8601 standard: a value between 1 and 7, inclusive, with Monday being 1, and Sunday 7. For an overview, see [ISO 8601 on Wikipedia](https://en.wikipedia.org/wiki/ISO_8601#Week_dates). Usage example: @@ -212,7 +224,7 @@ dt = new Temporal.DateTime(1995, 12, 7, 3, 24, 30, 0, 3, 500); ### datetime.**dayOfYear** : number The `dayOfYear` read-only property gives the ordinal day of the year that the date falls on. -This is a value between 1 and 365, or 366 in a leap year. +For the ISO 8601 calendar, this is a value between 1 and 365, or 366 in a leap year. Usage example: ```javascript @@ -224,7 +236,7 @@ console.log(dt.year, dt.dayOfYear); // => 1995 341 ### datetime.**weekOfYear** : number The `weekOfYear` read-only property gives the ISO week number of the date. -This is normally a value between 1 and 52, but in a few cases it can be 53 as well. +For the ISO 8601 calendar, this is normally a value between 1 and 52, but in a few cases it can be 53 as well. ISO week 1 is the week containing the first Thursday of the year. For more information on ISO week numbers, see for example the Wikipedia article on [ISO week date](https://en.wikipedia.org/wiki/ISO_week_date). @@ -238,7 +250,7 @@ console.log(dt.year, dt.weekOfYear, dt.dayOfWeek); // => 1995 49 4 ### datetime.**daysInMonth** : number The `daysInMonth` read-only property gives the number of days in the month that the date falls in. -This is 28, 29, 30, or 31, depending on the month and whether the year is a leap year. +For the ISO 8601 calendar, this is 28, 29, 30, or 31, depending on the month and whether the year is a leap year. Usage example: ```javascript @@ -261,7 +273,7 @@ console.log(poem); ### datetime.**daysInYear** : number The `daysInYear` read-only property gives the number of days in the year that the date falls in. -This is 365 or 366, depending on whether the year is a leap year. +For the ISO 8601 calendar, this is 365 or 366, depending on whether the year is a leap year. Usage example: ```javascript @@ -305,6 +317,9 @@ Since `Temporal.DateTime` objects are immutable, use this method instead of modi > **NOTE**: The allowed values for the `dateTimeLike.month` property start at 1, which is different from legacy `Date` where months are represented by zero-based indices (0 to 11). +> **NOTE**: If a `calendar` property is provided on `dateTimeLike`, the new calendar is applied first, before any of the other properties. +> If you are passing in an object with _only_ a `calendar` property, it is recommended to use the `withCalendar` method instead. + Usage example: ```javascript dt = new Temporal.DateTime(1995, 12, 7, 3, 24, 30, 0, 3, 500); @@ -574,7 +589,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, [propName: string]: unknown } +### 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 } **Returns:** a plain object with properties equal to the fields of `datetime`. @@ -598,10 +613,18 @@ Object.assign({}, dt.getFields()).day // => 7 This method is mainly useful if you are implementing a custom calendar. Most code will not need to use it. -Use `datetime.getFields()` instead. +Use `datetime.getFields()` instead, or `datetime.withCalendar('iso8601').getFields()`. Usage example: ```javascript dt = Temporal.Date.from('1995-12-07T03:24:30.000003500'); date.getISOCalendarFields().day // => 7 + +// Date in other calendar +dt = dt.withCalendar('hebrew'); +dt.getFields().day // => 14 +dt.getISOCalendarFields().day // => 7 + +// Most likely what you need is this: +dt.withCalendar('iso8601').day // => 7 ``` diff --git a/docs/monthday.md b/docs/monthday.md index 11a2bff594..7e0576ad66 100644 --- a/docs/monthday.md +++ b/docs/monthday.md @@ -13,11 +13,12 @@ A `Temporal.MonthDay` can be converted into a `Temporal.Date` by combining it wi ## Constructor -### **new Temporal.MonthDay**(_isoMonth_: number, _isoDay_: number, _refISOYear_?: number) : Temporal.MonthDay +### **new Temporal.MonthDay**(_isoMonth_: number, _isoDay_: number, _calendar_?: Temporal.Calendar, _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. - `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. @@ -25,7 +26,7 @@ A `Temporal.MonthDay` can be converted into a `Temporal.Date` by combining it wi **Returns:** a new `Temporal.MonthDay` object. Use this constructor if you have the correct parameters for the date already as individual number values, or you are implementing a custom calendar. -Otherwise, `Temporal.MonthDay.from()`, which accepts more kinds of input and allows disambiguation behaviour, is probably more convenient. +Otherwise, `Temporal.MonthDay.from()`, which accepts more kinds of input, allows inputting dates in different calendar reckonings, and allows disambiguation behaviour, is probably more convenient. All values are given as reckoned in the [ISO 8601 calendar](https://en.wikipedia.org/wiki/ISO_8601#Dates). Together, `refISOYear`, `isoMonth` and `isoDay` must represent a valid date in that calendar. @@ -57,7 +58,7 @@ md = new Temporal.MonthDay(2, 29) // => 02-29 This static method creates a new `Temporal.MonthDay` object from another value. If the value is another `Temporal.MonthDay` object, a new object representing the same month and day is returned. -If the value is any other object, it must have `month` and `day` properties, and a `Temporal.MonthDay` will be constructed from them. +If the value is any other object, it must have `month` and `day` properties, and optionally a `calendar` property, and a `Temporal.MonthDay` will be constructed from these properties. Any non-object value will be converted to a string, which is expected to be in ISO 8601 format. Any parts of the string other than the month and the day are optional and will be ignored. @@ -111,6 +112,10 @@ md.month // => 8 md.day // => 24 ``` +### monthDay.**calendar** : Temporal.Calendar + +The `calendar` read-only property gives the calendar that the `month` and `day` properties are interpreted in. + ## Methods ### monthDay.**with**(_monthDayLike_: object, _options_?: object) : Temporal.MonthDay @@ -137,6 +142,10 @@ The disambiguation parameter tells what should happen when out-of-range values a Since `Temporal.MonthDay` objects are immutable, use this method instead of modifying one. +> **NOTE**: Unlike in `Temporal.Date.prototype.with()`, a `calendar` property is not allowed on `monthDayLike`. +> It is not possible to convert a `Temporal.MonthDay` to another calendar system without knowing the year. +> If you need to do this, use `monthDay.withYear(year).withCalendar(calendar).getMonthDay()`. + Usage example: ```javascript md = Temporal.MonthDay.from('11-15'); @@ -263,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, [propName: string]: unknown } +### monthDay.**getFields**() : { month: number, day: number, calendar: Temporal.Calendar, [propName: string]: unknown } **Returns:** a plain object with properties equal to the fields of `monthDay`. diff --git a/docs/yearmonth.md b/docs/yearmonth.md index fef98ba9a5..427d185486 100644 --- a/docs/yearmonth.md +++ b/docs/yearmonth.md @@ -13,21 +13,22 @@ A `Temporal.YearMonth` can be converted into a `Temporal.Date` by combining it w ## Constructor -### **new Temporal.YearMonth**(_isoYear_: number, _isoMonth_: number, _refISODay_: number = 1) : Temporal.YearMonth +### **new Temporal.YearMonth**(_isoYear_: number, _isoMonth_: number, _calendar_?: Temporal.Calendar, _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. - `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. **Returns:** a new `Temporal.YearMonth` object. -Use this constructor if you have the correct parameters already as individual number values, or you are implementing a custom calendar. -Otherwise, `Temporal.YearMonth.from()`, which accepts more kinds of input and allows disambiguation behaviour, is probably more convenient. +Use this constructor if you have the correct parameters already as individual number values in the ISO 8601 calendar, or you are implementing a custom calendar. +Otherwise, `Temporal.YearMonth.from()`, which accepts more kinds of input, allows months in other calendar systems, and allows disambiguation behaviour, is probably more convenient. All values are given as reckoned in the [ISO 8601 calendar](https://en.wikipedia.org/wiki/ISO_8601#Dates). -Together, `isoYear`, `isoMonth`, and `refISODay` must represent a valid date in that calendar. +Together, `isoYear`, `isoMonth`, and `refISODay` must represent a valid date in that calendar, even if you are passing a different calendar as the `calendar` parameter. The range of allowed values for this type is exactly enough that calling [`getYearMonth()`](./date.html#getYearMonth) on any valid `Temporal.Date` will succeed. If `isoYear` and `isoMonth` are outside of this range, then `constrain` mode will clamp the date to the limit of the allowed range. @@ -135,6 +136,10 @@ ym.year // => 2019 ym.month // => 6 ``` +### yearMonth.**calendar** : Temporal.Calendar + +The `calendar` read-only property gives the calendar that the `year` and `month` properties are interpreted in. + ### yearMonth.**daysInMonth** : number The `daysInMonth` read-only property gives the number of days in the month. @@ -205,6 +210,10 @@ Since `Temporal.YearMonth` objects are immutable, use this method instead of mod > **NOTE**: The allowed values for the `yearMonthLike.month` property start at 1, which is different from legacy `Date` where months are represented by zero-based indices (0 to 11). +> **NOTE**: Unlike in `Temporal.Date.prototype.with()`, a `calendar` property is not allowed on `yearMonthLike`. +> It is not possible to convert a `Temporal.YearMonth` to another calendar system without knowing the day of the month. +> If you need to do this, use `yearMonth.withDay(day).withCalendar(calendar).getYearMonth()`. + Usage example: ```javascript ym = Temporal.YearMonth.from('2019-06'); @@ -429,7 +438,7 @@ ym = Temporal.YearMonth.from('2019-06'); ym.withDay(24) // => 2019-06-24 ``` -### yearMonth.**getFields**() : { year: number, month: number, [propName: string]: unknown } +### yearMonth.**getFields**() : { year: number, month: number, calendar: Temporal.Calendar, [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 4a57b7dd2d..3c9082403f 100644 --- a/polyfill/index.d.ts +++ b/polyfill/index.d.ts @@ -271,6 +271,14 @@ export namespace Temporal { year?: number; month?: number; day?: number; + calendar?: string | Temporal.Calendar; + }; + + type DateFields = { + year: number; + month: number; + day: number; + calendar: Temporal.Calendar; }; type DateISOCalendarFields = { @@ -291,10 +299,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); + constructor(isoYear: number, isoMonth: number, isoDay: number, calendar?: Temporal.Calendar); readonly year: number; readonly month: number; readonly day: number; + readonly calendar: Temporal.Calendar; readonly dayOfWeek: number; readonly dayOfYear: number; readonly weekOfYear: number; @@ -312,7 +321,7 @@ export namespace Temporal { withTime(temporalTime: Temporal.Time): Temporal.DateTime; getYearMonth(): Temporal.YearMonth; getMonthDay(): Temporal.MonthDay; - getFields(): Required; + getFields(): DateFields; getISOCalendarFields(): DateISOCalendarFields; toLocaleString(locales?: string | string[], options?: Intl.DateTimeFormatOptions): string; toJSON(): string; @@ -329,6 +338,20 @@ export namespace Temporal { millisecond?: number; microsecond?: number; nanosecond?: number; + calendar?: string | Temporal.Calendar; + }; + + type DateTimeFields = { + year: number; + month: number; + day: number; + hour: number; + minute: number; + second: number; + millisecond: number; + microsecond: number; + nanosecond: number; + calendar: Temporal.Calendar; }; type DateTimeISOCalendarFields = { @@ -365,7 +388,8 @@ export namespace Temporal { second?: number, millisecond?: number, microsecond?: number, - nanosecond?: number + nanosecond?: number, + calendar?: Temporal.Calendar ); readonly year: number; readonly month: number; @@ -376,6 +400,7 @@ export namespace Temporal { readonly millisecond: number; readonly microsecond: number; readonly nanosecond: number; + readonly calendar: Temporal.Calendar; readonly dayOfWeek: number; readonly dayOfYear: number; readonly weekOfYear: number; @@ -395,7 +420,7 @@ export namespace Temporal { getYearMonth(): Temporal.YearMonth; getMonthDay(): Temporal.MonthDay; getTime(): Temporal.Time; - getFields(): Required; + getFields(): DateTimeFields; getISOCalendarFields(): DateTimeISOCalendarFields; toLocaleString(locales?: string | string[], options?: Intl.DateTimeFormatOptions): string; toJSON(): string; @@ -407,6 +432,12 @@ export namespace Temporal { day?: number; }; + type MonthDayFields = { + month: number; + day: number; + calendar: Temporal.Calendar; + }; + /** * A `Temporal.MonthDay` represents a particular day on the calendar, but * without a year. For example, it could be used to represent a yearly @@ -416,13 +447,14 @@ export namespace Temporal { */ export class MonthDay implements Required { static from(item: Temporal.MonthDay | MonthDayLike | string, options?: AssignmentOptions): Temporal.MonthDay; - constructor(isoMonth: number, isoDay: number); + constructor(isoMonth: number, isoDay: number, calendar?: Temporal.Calendar, refISOYear?: number); readonly month: number; readonly day: number; + readonly calendar: Temporal.Calendar; equals(other: Temporal.MonthDay): boolean; with(monthDayLike: MonthDayLike, options?: AssignmentOptions): Temporal.MonthDay; withYear(year: number | { year: number }): Temporal.Date; - getFields(): Required; + getFields(): MonthDayFields; getISOCalendarFields(): DateISOCalendarFields; toLocaleString(locales?: string | string[], options?: Intl.DateTimeFormatOptions): string; toJSON(): string; @@ -514,6 +546,12 @@ export namespace Temporal { month?: number; }; + type YearMonthFields = { + year: number; + month: number; + calendar: Temporal.Calendar; + }; + /** * A `Temporal.YearMonth` represents a particular month on the calendar. For * example, it could be used to represent a particular instance of a monthly @@ -524,9 +562,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); + constructor(isoYear: number, isoMonth: number, calendar?: Temporal.Calendar, refISODay?: number); readonly year: number; readonly month: number; + readonly calendar: Temporal.Calendar; readonly daysInMonth: number; readonly daysInYear: number; readonly isLeapYear: boolean; @@ -536,7 +575,7 @@ export namespace Temporal { minus(durationLike: Temporal.Duration | DurationLike, options: ArithmeticOptions): Temporal.YearMonth; difference(other: Temporal.YearMonth, options: DifferenceOptions<'years' | 'months'>): Temporal.Duration; withDay(day: number): Temporal.Date; - getFields(): Required; + getFields(): YearMonthFields; getISOCalendarFields(): DateISOCalendarFields; toLocaleString(locales?: string | string[], options?: Intl.DateTimeFormatOptions): string; toJSON(): string; diff --git a/polyfill/lib/calendar.mjs b/polyfill/lib/calendar.mjs index 3b2ba9b54d..69842f9322 100644 --- a/polyfill/lib/calendar.mjs +++ b/polyfill/lib/calendar.mjs @@ -1,6 +1,6 @@ import { ES } from './ecmascript.mjs'; -import { MakeIntrinsicClass } from './intrinsicclass.mjs'; -import { CALENDAR_ID, CreateSlots, GetSlot, SetSlot } from './slots.mjs'; +import { GetIntrinsic, MakeIntrinsicClass } from './intrinsicclass.mjs'; +import { CALENDAR_ID, ISO_YEAR, ISO_MONTH, ISO_DAY, CreateSlots, GetSlot, SetSlot } from './slots.mjs'; export class Calendar { constructor(id) { @@ -103,3 +103,153 @@ export class Calendar { } MakeIntrinsicClass(Calendar, 'Temporal.Calendar'); + +export class Iso8601 extends Calendar { + constructor() { + super('iso8601'); + } + dateFromFields(fields, options, constructor) { + if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver'); + const disambiguation = ES.ToTemporalDisambiguation(options); + // Intentionally alphabetical + let { year, month, day } = ES.ToRecord(fields, [['day'], ['month'], ['year']]); + ({ year, month, day } = ES.RegulateDate(year, month, day, disambiguation)); + return new constructor(year, month, day, this); + } + dateTimeFromFields(fields, options, constructor) { + if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver'); + const disambiguation = ES.ToTemporalDisambiguation(options); + // Intentionally alphabetical + let { year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = ES.ToRecord(fields, [ + ['day'], + ['hour', 0], + ['microsecond', 0], + ['millisecond', 0], + ['minute', 0], + ['month'], + ['nanosecond', 0], + ['second', 0], + ['year'] + ]); + ({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = ES.RegulateDateTime( + year, + month, + day, + hour, + minute, + second, + millisecond, + microsecond, + nanosecond, + disambiguation + )); + return new constructor(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, this); + } + yearMonthFromFields(fields, options, constructor) { + if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver'); + const disambiguation = ES.ToTemporalDisambiguation(options); + // Intentionally alphabetical + let { year, month } = ES.ToRecord(fields, [['month'], ['year']]); + ({ year, month } = ES.RegulateYearMonth(year, month, disambiguation)); + return new constructor(year, month, this, /* refIsoDay = */ 1); + } + monthDayFromFields(fields, options, constructor) { + if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver'); + const disambiguation = ES.ToTemporalDisambiguation(options); + // Intentionally alphabetical + let { month, day } = ES.ToRecord(fields, [['day'], ['month']]); + ({ month, day } = ES.RegulateMonthDay(month, day, disambiguation)); + return new constructor(month, day, this, /* refIsoYear = */ 1972); + } + plus(date, duration, options, constructor) { + if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver'); + const disambiguation = ES.ToTemporalDisambiguation(options); + + const { years, months, weeks, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = duration; + const { days } = ES.BalanceDuration( + duration.days, + hours, + minutes, + seconds, + milliseconds, + microseconds, + nanoseconds, + 'days' + ); + + let year = GetSlot(date, ISO_YEAR); + let month = GetSlot(date, ISO_MONTH); + let day = GetSlot(date, ISO_DAY); + ({ year, month, day } = ES.AddDate(year, month, day, years, months, weeks, days, disambiguation)); + ({ year, month, day } = ES.RegulateDate(year, month, day, disambiguation)); + + return new constructor(year, month, day, this); + } + minus(date, duration, options, constructor) { + if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver'); + const disambiguation = ES.ToTemporalDisambiguation(options); + + const { years, months, weeks, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = duration; + const { days } = ES.BalanceDuration( + duration.days, + hours, + minutes, + seconds, + milliseconds, + microseconds, + nanoseconds, + 'days' + ); + + let year = GetSlot(date, ISO_YEAR); + let month = GetSlot(date, ISO_MONTH); + let day = GetSlot(date, ISO_DAY); + ({ year, month, day } = ES.SubtractDate(year, month, day, years, months, weeks, days, disambiguation)); + ({ year, month, day } = ES.RegulateDate(year, month, day, disambiguation)); + + return new constructor(year, month, day, this); + } + difference(smaller, larger, options) { + if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver'); + const largestUnit = ES.ToLargestTemporalUnit(options, 'days', ['hours', 'minutes', 'seconds']); + const { years, months, weeks, days } = ES.DifferenceDate(smaller, larger, largestUnit); + const Duration = GetIntrinsic('%Temporal.Duration%'); + return new Duration(years, months, weeks, days, 0, 0, 0, 0, 0, 0); + } + year(date) { + if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver'); + return GetSlot(date, ISO_YEAR); + } + month(date) { + if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver'); + return GetSlot(date, ISO_MONTH); + } + day(date) { + if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver'); + return GetSlot(date, ISO_DAY); + } + dayOfWeek(date) { + if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver'); + return ES.DayOfWeek(GetSlot(date, ISO_YEAR), GetSlot(date, ISO_MONTH), GetSlot(date, ISO_DAY)); + } + dayOfYear(date) { + if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver'); + return ES.DayOfYear(GetSlot(date, ISO_YEAR), GetSlot(date, ISO_MONTH), GetSlot(date, ISO_DAY)); + } + weekOfYear(date) { + if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver'); + return ES.WeekOfYear(GetSlot(date, ISO_YEAR), GetSlot(date, ISO_MONTH), GetSlot(date, ISO_DAY)); + } + daysInMonth(date) { + if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver'); + return ES.DaysInMonth(GetSlot(date, ISO_YEAR), GetSlot(date, ISO_MONTH)); + } + daysInYear(date) { + if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver'); + return ES.LeapYear(GetSlot(date, ISO_YEAR)) ? 366 : 365; + } + isLeapYear(date) { + if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver'); + return ES.LeapYear(GetSlot(date, ISO_YEAR)); + } +} diff --git a/polyfill/lib/date.mjs b/polyfill/lib/date.mjs index 3d4aeba612..e9e5c644e4 100644 --- a/polyfill/lib/date.mjs +++ b/polyfill/lib/date.mjs @@ -10,126 +10,116 @@ import { MILLISECOND, MICROSECOND, NANOSECOND, + CALENDAR, CreateSlots, GetSlot, SetSlot } from './slots.mjs'; +const ObjectAssign = Object.assign; + export class Date { - constructor(isoYear, isoMonth, isoDay) { + constructor(isoYear, isoMonth, isoDay, calendar = undefined) { isoYear = ES.ToInteger(isoYear); isoMonth = ES.ToInteger(isoMonth); isoDay = ES.ToInteger(isoDay); + if (calendar === undefined) calendar = ES.GetDefaultCalendar(); ES.RejectDate(isoYear, isoMonth, isoDay); + if (!calendar || typeof calendar !== 'object') throw new RangeError('invalid calendar'); CreateSlots(this); SetSlot(this, ISO_YEAR, isoYear); SetSlot(this, ISO_MONTH, isoMonth); SetSlot(this, ISO_DAY, isoDay); + SetSlot(this, CALENDAR, calendar); } get year() { if (!ES.IsTemporalDate(this)) throw new TypeError('invalid receiver'); - return GetSlot(this, ISO_YEAR); + return GetSlot(this, CALENDAR).year(this); } get month() { if (!ES.IsTemporalDate(this)) throw new TypeError('invalid receiver'); - return GetSlot(this, ISO_MONTH); + return GetSlot(this, CALENDAR).month(this); } get day() { if (!ES.IsTemporalDate(this)) throw new TypeError('invalid receiver'); - return GetSlot(this, ISO_DAY); + return GetSlot(this, CALENDAR).day(this); + } + get calendar() { + if (!ES.IsTemporalDate(this)) throw new TypeError('invalid receiver'); + return GetSlot(this, CALENDAR); } get dayOfWeek() { if (!ES.IsTemporalDate(this)) throw new TypeError('invalid receiver'); - return ES.DayOfWeek(GetSlot(this, ISO_YEAR), GetSlot(this, ISO_MONTH), GetSlot(this, ISO_DAY)); + return GetSlot(this, CALENDAR).dayOfWeek(this); } get dayOfYear() { if (!ES.IsTemporalDate(this)) throw new TypeError('invalid receiver'); - return ES.DayOfYear(GetSlot(this, ISO_YEAR), GetSlot(this, ISO_MONTH), GetSlot(this, ISO_DAY)); + return GetSlot(this, CALENDAR).dayOfYear(this); } get weekOfYear() { if (!ES.IsTemporalDate(this)) throw new TypeError('invalid receiver'); - return ES.WeekOfYear(GetSlot(this, ISO_YEAR), GetSlot(this, ISO_MONTH), GetSlot(this, ISO_DAY)); + return GetSlot(this, CALENDAR).weekOfYear(this); } get daysInYear() { if (!ES.IsTemporalDate(this)) throw new TypeError('invalid receiver'); - return ES.LeapYear(GetSlot(this, ISO_YEAR)) ? 366 : 365; + return GetSlot(this, CALENDAR).daysInYear(this); } get daysInMonth() { if (!ES.IsTemporalDate(this)) throw new TypeError('invalid receiver'); - return ES.DaysInMonth(GetSlot(this, ISO_YEAR), GetSlot(this, ISO_MONTH)); + return GetSlot(this, CALENDAR).daysInMonth(this); } get isLeapYear() { if (!ES.IsTemporalDate(this)) throw new TypeError('invalid receiver'); - return ES.LeapYear(GetSlot(this, ISO_YEAR)); + return GetSlot(this, CALENDAR).isLeapYear(this); } with(temporalDateLike = {}, options) { if (!ES.IsTemporalDate(this)) throw new TypeError('invalid receiver'); - const disambiguation = ES.ToTemporalDisambiguation(options); + let source; + let calendar = temporalDateLike.calendar; + if (calendar) { + const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%'); + calendar = TemporalCalendar.from(calendar); + source = new Date(GetSlot(this, ISO_YEAR), GetSlot(this, ISO_MONTH), GetSlot(this, ISO_DAY), calendar); + } else { + calendar = GetSlot(this, CALENDAR); + source = this; + } const props = ES.ToPartialRecord(temporalDateLike, ['day', 'month', 'year']); if (!props) { throw new RangeError('invalid date-like'); } - let { year = GetSlot(this, ISO_YEAR), month = GetSlot(this, ISO_MONTH), day = GetSlot(this, ISO_DAY) } = props; - ({ year, month, day } = ES.RegulateDate(year, month, day, disambiguation)); + const fields = ES.ToRecord(source, [['day'], ['month'], ['year']]); + ObjectAssign(fields, props); const Construct = ES.SpeciesConstructor(this, Date); - const result = new Construct(year, month, day); + const result = calendar.dateFromFields(fields, options, Construct); if (!ES.IsTemporalDate(result)) throw new TypeError('invalid result'); return result; } plus(temporalDurationLike, options) { if (!ES.IsTemporalDate(this)) throw new TypeError('invalid receiver'); - const disambiguation = ES.ToTemporalDisambiguation(options); const duration = ES.ToLimitedTemporalDuration(temporalDurationLike); - let { year, month, day } = this; - const { years, months, weeks, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = duration; - const { days } = ES.BalanceDuration( - duration.days, - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, - 'days' - ); - ({ year, month, day } = ES.AddDate(year, month, day, years, months, weeks, days, disambiguation)); - ({ year, month, day } = ES.RegulateDate(year, month, day, disambiguation)); const Construct = ES.SpeciesConstructor(this, Date); - const result = new Construct(year, month, day); + const result = GetSlot(this, CALENDAR).plus(this, duration, options, Construct); if (!ES.IsTemporalDate(result)) throw new TypeError('invalid result'); return result; } minus(temporalDurationLike, options) { if (!ES.IsTemporalDate(this)) throw new TypeError('invalid receiver'); - const disambiguation = ES.ToTemporalDisambiguation(options); const duration = ES.ToLimitedTemporalDuration(temporalDurationLike); - let { year, month, day } = this; - const { years, months, weeks, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = duration; - const { days } = ES.BalanceDuration( - duration.days, - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, - 'days' - ); - ({ year, month, day } = ES.SubtractDate(year, month, day, years, months, weeks, days, disambiguation)); - ({ year, month, day } = ES.RegulateDate(year, month, day, disambiguation)); const Construct = ES.SpeciesConstructor(this, Date); - const result = new Construct(year, month, day); + const result = GetSlot(this, CALENDAR).minus(this, duration, options, Construct); if (!ES.IsTemporalDate(result)) throw new TypeError('invalid result'); return result; } difference(other, options) { if (!ES.IsTemporalDate(this)) throw new TypeError('invalid receiver'); if (!ES.IsTemporalDate(other)) throw new TypeError('invalid Date object'); - const largestUnit = ES.ToLargestTemporalUnit(options, 'days', ['hours', 'minutes', 'seconds']); + const calendar = GetSlot(this, CALENDAR); + if (calendar.id !== GetSlot(other, CALENDAR).id) { + other = new Date(GetSlot(other, ISO_YEAR), GetSlot(other, ISO_MONTH), GetSlot(other, ISO_DAY), calendar); + } const [smaller, larger] = [this, other].sort(Date.compare); - const { years, months, weeks, days } = ES.DifferenceDate(smaller, larger, largestUnit); - const Duration = GetIntrinsic('%Temporal.Duration%'); - return new Duration(years, months, weeks, days, 0, 0, 0, 0, 0, 0); + return calendar.difference(smaller, larger, options); } equals(other) { if (!ES.IsTemporalDate(this)) throw new TypeError('invalid receiver'); @@ -139,7 +129,7 @@ export class Date { const val2 = GetSlot(other, slot); if (val1 !== val2) return false; } - return true; + return GetSlot(this, CALENDAR).id === GetSlot(other, CALENDAR).id; } toString() { if (!ES.IsTemporalDate(this)) throw new TypeError('invalid receiver'); @@ -162,6 +152,7 @@ export class Date { const year = GetSlot(this, ISO_YEAR); const month = GetSlot(this, ISO_MONTH); const day = GetSlot(this, ISO_DAY); + const calendar = GetSlot(this, CALENDAR); const hour = GetSlot(temporalTime, HOUR); const minute = GetSlot(temporalTime, MINUTE); const second = GetSlot(temporalTime, SECOND); @@ -169,21 +160,26 @@ export class Date { const microsecond = GetSlot(temporalTime, MICROSECOND); const nanosecond = GetSlot(temporalTime, NANOSECOND); const DateTime = GetIntrinsic('%Temporal.DateTime%'); - return new DateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond); + return new DateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, calendar); } getYearMonth() { if (!ES.IsTemporalDate(this)) throw new TypeError('invalid receiver'); const YearMonth = GetIntrinsic('%Temporal.YearMonth%'); - return new YearMonth(GetSlot(this, ISO_YEAR), GetSlot(this, ISO_MONTH)); + const calendar = GetSlot(this, CALENDAR); + const fields = ES.ToRecord(this, [['month'], ['year']]); + return calendar.yearMonthFromFields(fields, {}, YearMonth); } getMonthDay() { if (!ES.IsTemporalDate(this)) throw new TypeError('invalid receiver'); const MonthDay = GetIntrinsic('%Temporal.MonthDay%'); - return new MonthDay(GetSlot(this, ISO_MONTH), GetSlot(this, ISO_DAY)); + const calendar = GetSlot(this, CALENDAR); + const fields = ES.ToRecord(this, [['day'], ['month']]); + return calendar.monthDayFromFields(fields, {}, MonthDay); } getFields() { const fields = ES.ToRecord(this, [['day'], ['month'], ['year']]); if (!fields) throw new TypeError('invalid receiver'); + fields.calendar = GetSlot(this, CALENDAR); return fields; } getISOCalendarFields() { @@ -196,21 +192,27 @@ export class Date { } static from(item, options = undefined) { const disambiguation = ES.ToTemporalDisambiguation(options); - let year, month, day; + const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%'); + let result; if (typeof item === 'object' && item) { if (ES.IsTemporalDate(item)) { - year = GetSlot(item, ISO_YEAR); - month = GetSlot(item, ISO_MONTH); - day = GetSlot(item, ISO_DAY); + const year = GetSlot(item, ISO_YEAR); + const month = GetSlot(item, ISO_MONTH); + const day = GetSlot(item, ISO_DAY); + const calendar = GetSlot(item, CALENDAR); + result = new this(year, month, day, calendar); } else { - // Intentionally alphabetical - ({ year, month, day } = ES.ToRecord(item, [['day'], ['month'], ['year']])); + let calendar = item.calendar; + if (calendar === undefined) calendar = ES.GetDefaultCalendar(); + calendar = TemporalCalendar.from(calendar); + result = calendar.dateFromFields(item, options, this); } } else { - ({ year, month, day } = ES.ParseTemporalDateString(ES.ToString(item))); + let { year, month, day } = ES.ParseTemporalDateString(ES.ToString(item)); + ({ year, month, day } = ES.RegulateDate(year, month, day, disambiguation)); + const calendar = ES.GetDefaultCalendar(); + result = new this(year, month, day, calendar); } - ({ year, month, day } = ES.RegulateDate(year, month, day, disambiguation)); - const result = new this(year, month, day); if (!ES.IsTemporalDate(result)) throw new TypeError('invalid result'); return result; } @@ -221,7 +223,9 @@ export class Date { const val2 = GetSlot(two, slot); if (val1 !== val2) return ES.ComparisonResult(val1 - val2); } - return ES.ComparisonResult(0); + const cal1 = GetSlot(one, CALENDAR).id; + const cal2 = GetSlot(two, CALENDAR).id; + return ES.ComparisonResult(cal1 < cal2 ? -1 : cal1 > cal2 ? 1 : 0); } } Date.prototype.toJSON = Date.prototype.toString; diff --git a/polyfill/lib/datetime.mjs b/polyfill/lib/datetime.mjs index 441cd53ffb..8c0ea3d72c 100644 --- a/polyfill/lib/datetime.mjs +++ b/polyfill/lib/datetime.mjs @@ -11,11 +11,14 @@ import { MILLISECOND, MICROSECOND, NANOSECOND, + CALENDAR, CreateSlots, GetSlot, SetSlot } from './slots.mjs'; +const ObjectAssign = Object.assign; + export class DateTime { constructor( isoYear, @@ -26,7 +29,8 @@ export class DateTime { second = 0, millisecond = 0, microsecond = 0, - nanosecond = 0 + nanosecond = 0, + calendar = undefined ) { isoYear = ES.ToInteger(isoYear); isoMonth = ES.ToInteger(isoMonth); @@ -37,7 +41,9 @@ export class DateTime { millisecond = ES.ToInteger(millisecond); microsecond = ES.ToInteger(microsecond); nanosecond = ES.ToInteger(nanosecond); + if (calendar === undefined) calendar = ES.GetDefaultCalendar(); ES.RejectDateTime(isoYear, isoMonth, isoDay, hour, minute, second, millisecond, microsecond, nanosecond); + if (!calendar || typeof calendar !== 'object') throw new RangeError('invalid calendar'); CreateSlots(this); SetSlot(this, ISO_YEAR, isoYear); SetSlot(this, ISO_MONTH, isoMonth); @@ -48,18 +54,19 @@ export class DateTime { SetSlot(this, MILLISECOND, millisecond); SetSlot(this, MICROSECOND, microsecond); SetSlot(this, NANOSECOND, nanosecond); + SetSlot(this, CALENDAR, calendar); } get year() { if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver'); - return GetSlot(this, ISO_YEAR); + return GetSlot(this, CALENDAR).year(this); } get month() { if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver'); - return GetSlot(this, ISO_MONTH); + return GetSlot(this, CALENDAR).month(this); } get day() { if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver'); - return GetSlot(this, ISO_DAY); + return GetSlot(this, CALENDAR).day(this); } get hour() { if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver'); @@ -85,33 +92,57 @@ export class DateTime { if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver'); return GetSlot(this, NANOSECOND); } + get calendar() { + if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver'); + return GetSlot(this, CALENDAR); + } get dayOfWeek() { if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver'); - return ES.DayOfWeek(GetSlot(this, ISO_YEAR), GetSlot(this, ISO_MONTH), GetSlot(this, ISO_DAY)); + return GetSlot(this, CALENDAR).dayOfWeek(this); } get dayOfYear() { if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver'); - return ES.DayOfYear(GetSlot(this, ISO_YEAR), GetSlot(this, ISO_MONTH), GetSlot(this, ISO_DAY)); + return GetSlot(this, CALENDAR).dayOfYear(this); } get weekOfYear() { if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver'); - return ES.WeekOfYear(GetSlot(this, ISO_YEAR), GetSlot(this, ISO_MONTH), GetSlot(this, ISO_DAY)); + return GetSlot(this, CALENDAR).weekOfYear(this); } get daysInYear() { if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver'); - return ES.LeapYear(GetSlot(this, ISO_YEAR)) ? 366 : 365; + return GetSlot(this, CALENDAR).daysInYear(this); } get daysInMonth() { if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver'); - return ES.DaysInMonth(GetSlot(this, ISO_YEAR), GetSlot(this, ISO_MONTH)); + return GetSlot(this, CALENDAR).daysInMonth(this); } get isLeapYear() { if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver'); - return ES.LeapYear(GetSlot(this, ISO_YEAR)); + return GetSlot(this, CALENDAR).isLeapYear(this); } with(temporalDateTimeLike, options) { if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver'); - const disambiguation = ES.ToTemporalDisambiguation(options); + let source; + let calendar = temporalDateTimeLike.calendar; + if (calendar) { + const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%'); + calendar = TemporalCalendar.from(calendar); + source = new DateTime( + GetSlot(this, ISO_YEAR), + GetSlot(this, ISO_MONTH), + GetSlot(this, ISO_DAY), + GetSlot(this, HOUR), + GetSlot(this, MINUTE), + GetSlot(this, SECOND), + GetSlot(this, MILLISECOND), + GetSlot(this, MICROSECOND), + GetSlot(this, NANOSECOND), + calendar + ); + } else { + calendar = GetSlot(this, CALENDAR); + source = this; + } const props = ES.ToPartialRecord(temporalDateTimeLike, [ 'day', 'hour', @@ -126,31 +157,20 @@ export class DateTime { if (!props) { throw new RangeError('invalid date-time-like'); } - let { - year = GetSlot(this, ISO_YEAR), - month = GetSlot(this, ISO_MONTH), - day = GetSlot(this, ISO_DAY), - hour = GetSlot(this, HOUR), - minute = GetSlot(this, MINUTE), - second = GetSlot(this, SECOND), - millisecond = GetSlot(this, MILLISECOND), - microsecond = GetSlot(this, MICROSECOND), - nanosecond = GetSlot(this, NANOSECOND) - } = props; - ({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = ES.RegulateDateTime( - year, - month, - day, - hour, - minute, - second, - millisecond, - microsecond, - nanosecond, - disambiguation - )); + const fields = ES.ToRecord(source, [ + ['day'], + ['hour'], + ['microsecond'], + ['millisecond'], + ['minute'], + ['month'], + ['nanosecond'], + ['second'], + ['year'] + ]); + ObjectAssign(fields, props); const Construct = ES.SpeciesConstructor(this, DateTime); - const result = new Construct(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond); + const result = calendar.dateTimeFromFields(fields, options, Construct); if (!ES.IsTemporalDateTime(result)) throw new TypeError('invalid result'); return result; } @@ -158,9 +178,32 @@ export class DateTime { if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver'); const disambiguation = ES.ToTemporalDisambiguation(options); const duration = ES.ToLimitedTemporalDuration(temporalDurationLike); - let { year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = this; - let { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = duration; - ({ year, month, day } = ES.AddDate(year, month, day, years, months, weeks, days, disambiguation)); + + // Delegate the date part addition to the calendar + const calendar = GetSlot(this, CALENDAR); + const Date = GetIntrinsic('%Temporal.Date%'); + const addedDate = calendar.plus( + new Date(GetSlot(this, ISO_YEAR), GetSlot(this, ISO_MONTH), GetSlot(this, ISO_DAY), calendar), + duration, + options, + Date + ); + let year = GetSlot(addedDate, ISO_YEAR); + let month = GetSlot(addedDate, ISO_MONTH); + let day = GetSlot(addedDate, ISO_DAY); + + // Add the time part + let { hour, minute, second, millisecond, microsecond, nanosecond } = this; + const { hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = ES.BalanceDuration( + duration.days, + duration.hours, + duration.minutes, + duration.seconds, + duration.milliseconds, + duration.microseconds, + duration.nanoseconds, + 'days' + ); let deltaDays = 0; ({ deltaDays, hour, minute, second, millisecond, microsecond, nanosecond } = ES.AddTime( hour, @@ -177,7 +220,6 @@ export class DateTime { nanoseconds )); day += deltaDays; - ({ year, month, day } = ES.BalanceDate(year, month, day)); ({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = ES.RegulateDateTime( year, month, @@ -199,8 +241,19 @@ export class DateTime { if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver'); const disambiguation = ES.ToTemporalDisambiguation(options); const duration = ES.ToLimitedTemporalDuration(temporalDurationLike); - let { year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = this; - let { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = duration; + + // Subtract the time part + let { hour, minute, second, millisecond, microsecond, nanosecond } = this; + const { hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = ES.BalanceDuration( + duration.days, + duration.hours, + duration.minutes, + duration.seconds, + duration.milliseconds, + duration.microseconds, + duration.nanoseconds, + 'days' + ); let deltaDays = 0; ({ deltaDays, hour, minute, second, millisecond, microsecond, nanosecond } = ES.SubtractTime( hour, @@ -216,8 +269,20 @@ export class DateTime { microseconds, nanoseconds )); - days -= deltaDays; - ({ year, month, day } = ES.SubtractDate(year, month, day, years, months, weeks, days, disambiguation)); + + // Delegate the date part subtraction to the calendar + const calendar = GetSlot(this, CALENDAR); + const Date = GetIntrinsic('%Temporal.Date%'); + const addedDate = calendar.minus( + new Date(GetSlot(this, ISO_YEAR), GetSlot(this, ISO_MONTH), GetSlot(this, ISO_DAY), calendar), + { ...duration, days: duration.days - deltaDays }, + options, + Date + ); + let year = GetSlot(addedDate, ISO_YEAR); + let month = GetSlot(addedDate, ISO_MONTH); + let day = GetSlot(addedDate, ISO_DAY); + ({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = ES.RegulateDateTime( year, month, @@ -238,25 +303,44 @@ export class DateTime { difference(other, options) { if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver'); if (!ES.IsTemporalDateTime(other)) throw new TypeError('invalid DateTime object'); + const calendar = GetSlot(this, CALENDAR); + if (calendar.id !== GetSlot(other, CALENDAR).id) { + other = new DateTime( + GetSlot(other, ISO_YEAR), + GetSlot(other, ISO_MONTH), + GetSlot(other, ISO_DAY), + GetSlot(other, HOUR), + GetSlot(other, MINUTE), + GetSlot(other, SECOND), + GetSlot(other, MILLISECOND), + GetSlot(other, MICROSECOND), + GetSlot(other, NANOSECOND), + calendar + ); + } const largestUnit = ES.ToLargestTemporalUnit(options, 'days'); const [smaller, larger] = [this, other].sort(DateTime.compare); let { deltaDays, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = ES.DifferenceTime( smaller, larger ); - let { year, month, day } = larger; - day += deltaDays; + let year = GetSlot(larger, ISO_YEAR); + let month = GetSlot(larger, ISO_MONTH); + let day = GetSlot(larger, ISO_DAY) + deltaDays; ({ year, month, day } = ES.BalanceDate(year, month, day)); + const Date = GetIntrinsic('%Temporal.Date%'); + const adjustedLarger = new Date(year, month, day, GetSlot(larger, CALENDAR)); let dateLargestUnit = 'days'; if (largestUnit === 'years' || largestUnit === 'months' || largestUnit === 'weeks') { dateLargestUnit = largestUnit; } + const dateOptions = ObjectAssign({}, options, { largestUnit: dateLargestUnit }); + const dateDifference = calendar.difference(smaller, adjustedLarger, dateOptions); - let { years, months, weeks, days } = ES.DifferenceDate(smaller, { year, month, day }, dateLargestUnit); - + let days; ({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = ES.BalanceDuration( - days, + dateDifference.days, hours, minutes, seconds, @@ -267,7 +351,18 @@ export class DateTime { )); const Duration = GetIntrinsic('%Temporal.Duration%'); - return new Duration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds); + return new Duration( + dateDifference.years, + dateDifference.months, + dateDifference.weeks, + days, + hours, + minutes, + seconds, + milliseconds, + microseconds, + nanoseconds + ); } equals(other) { if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver'); @@ -277,7 +372,7 @@ export class DateTime { const val2 = GetSlot(other, slot); if (val1 !== val2) return false; } - return true; + return GetSlot(this, CALENDAR).id === GetSlot(other, CALENDAR).id; } toString() { if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver'); @@ -312,17 +407,21 @@ export class DateTime { getDate() { if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver'); const Date = GetIntrinsic('%Temporal.Date%'); - return new Date(GetSlot(this, ISO_YEAR), GetSlot(this, ISO_MONTH), GetSlot(this, ISO_DAY)); + return new Date(GetSlot(this, ISO_YEAR), GetSlot(this, ISO_MONTH), GetSlot(this, ISO_DAY), GetSlot(this, CALENDAR)); } getYearMonth() { if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver'); const YearMonth = GetIntrinsic('%Temporal.YearMonth%'); - return new YearMonth(GetSlot(this, ISO_YEAR), GetSlot(this, ISO_MONTH)); + const calendar = GetSlot(this, CALENDAR); + const fields = ES.ToRecord(this, [['month'], ['year']]); + return calendar.yearMonthFromFields(fields, {}, YearMonth); } getMonthDay() { if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver'); const MonthDay = GetIntrinsic('%Temporal.MonthDay%'); - return new MonthDay(GetSlot(this, ISO_MONTH), GetSlot(this, ISO_DAY)); + const calendar = GetSlot(this, CALENDAR); + const fields = ES.ToRecord(this, [['day'], ['month']]); + return calendar.monthDayFromFields(fields, {}, MonthDay); } getTime() { if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver'); @@ -349,6 +448,7 @@ export class DateTime { ['year'] ]); if (!fields) throw new TypeError('invalid receiver'); + fields.calendar = GetSlot(this, CALENDAR); return fields; } getISOCalendarFields() { @@ -368,33 +468,29 @@ export class DateTime { static from(item, options = undefined) { const disambiguation = ES.ToTemporalDisambiguation(options); - let year, month, day, hour, minute, second, millisecond, microsecond, nanosecond; + const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%'); + let result; if (typeof item === 'object' && item) { if (ES.IsTemporalDateTime(item)) { - year = GetSlot(item, ISO_YEAR); - month = GetSlot(item, ISO_MONTH); - day = GetSlot(item, ISO_DAY); - hour = GetSlot(item, HOUR); - minute = GetSlot(item, MINUTE); - second = GetSlot(item, SECOND); - millisecond = GetSlot(item, MILLISECOND); - microsecond = GetSlot(item, MICROSECOND); - nanosecond = GetSlot(item, NANOSECOND); + const year = GetSlot(item, ISO_YEAR); + const month = GetSlot(item, ISO_MONTH); + const day = GetSlot(item, ISO_DAY); + const hour = GetSlot(item, HOUR); + const minute = GetSlot(item, MINUTE); + const second = GetSlot(item, SECOND); + const millisecond = GetSlot(item, MILLISECOND); + const microsecond = GetSlot(item, MICROSECOND); + const nanosecond = GetSlot(item, NANOSECOND); + const calendar = GetSlot(item, CALENDAR); + result = new this(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, calendar); } else { - ({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = ES.ToRecord(item, [ - ['day'], - ['hour', 0], - ['microsecond', 0], - ['millisecond', 0], - ['minute', 0], - ['month'], - ['nanosecond', 0], - ['second', 0], - ['year'] - ])); + let calendar = item.calendar; + if (calendar === undefined) calendar = ES.GetDefaultCalendar(); + calendar = TemporalCalendar.from(calendar); + result = calendar.dateTimeFromFields(item, options, this); } } else { - ({ + let { year, month, day, @@ -404,21 +500,22 @@ export class DateTime { millisecond, microsecond, nanosecond - } = ES.ParseTemporalDateTimeString(ES.ToString(item))); + } = ES.ParseTemporalDateTimeString(ES.ToString(item)); + ({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = ES.RegulateDateTime( + year, + month, + day, + hour, + minute, + second, + millisecond, + microsecond, + nanosecond, + disambiguation + )); + const calendar = ES.GetDefaultCalendar(); + result = new this(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, calendar); } - ({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = ES.RegulateDateTime( - year, - month, - day, - hour, - minute, - second, - millisecond, - microsecond, - nanosecond, - disambiguation - )); - const result = new this(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond); if (!ES.IsTemporalDateTime(result)) throw new TypeError('invalid result'); return result; } @@ -429,7 +526,9 @@ export class DateTime { const val2 = GetSlot(two, slot); if (val1 !== val2) return ES.ComparisonResult(val1 - val2); } - return ES.ComparisonResult(0); + const cal1 = GetSlot(one, CALENDAR).id; + const cal2 = GetSlot(two, CALENDAR).id; + return ES.ComparisonResult(cal1 < cal2 ? -1 : cal1 > cal2 ? 1 : 0); } } DateTime.prototype.toJSON = DateTime.prototype.toString; diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs index bd610a7055..4f3c4216de 100644 --- a/polyfill/lib/ecmascript.mjs +++ b/polyfill/lib/ecmascript.mjs @@ -6,6 +6,7 @@ const ObjectAssign = Object.assign; import bigInt from 'big-integer'; import { GetIntrinsic } from './intrinsicclass.mjs'; +import { Iso8601 as CalendarIso8601 } from './calendar.mjs'; import { GetSlot, HasSlot, @@ -42,6 +43,7 @@ const YEAR_MIN = -271821; const YEAR_MAX = 275760; const BUILTIN_CALENDARS = { + iso8601: CalendarIso8601 // To be filled in as builtin calendars are implemented }; @@ -83,6 +85,7 @@ export const ES = ObjectAssign({}, ES2019, { if (!(id in BUILTIN_CALENDARS)) throw new RangeError(`unknown calendar ${id}`); return new BUILTIN_CALENDARS[id](); }, + GetDefaultCalendar: () => ES.GetBuiltinCalendar('iso8601'), ParseISODateTime: (isoString, { zoneRequired }) => { const regex = zoneRequired ? PARSE.absolute : PARSE.datetime; const match = regex.exec(isoString); @@ -456,7 +459,13 @@ export const ES = ObjectAssign({}, ES2019, { const value = bag[property]; if (value !== undefined) { any = any || {}; - any[property] = ES.ToInteger(value); + if (property === 'calendar') { + // FIXME: this is terrible + const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%'); + any.calendar = TemporalCalendar.from(value); + } else { + any[property] = ES.ToInteger(value); + } } } return any ? any : false; @@ -472,7 +481,13 @@ export const ES = ObjectAssign({}, ES2019, { } value = defaultValue; } - result[property] = ES.ToInteger(value); + if (property === 'calendar') { + // FIXME: this is terrible + const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%'); + result.calendar = TemporalCalendar.from(value); + } else { + result[property] = ES.ToInteger(value); + } } return result; }, diff --git a/polyfill/lib/monthday.mjs b/polyfill/lib/monthday.mjs index 57fc16befb..b4a40afe20 100644 --- a/polyfill/lib/monthday.mjs +++ b/polyfill/lib/monthday.mjs @@ -1,40 +1,51 @@ import { ES } from './ecmascript.mjs'; import { GetIntrinsic, MakeIntrinsicClass } from './intrinsicclass.mjs'; -import { ISO_MONTH, ISO_DAY, REF_ISO_YEAR, CreateSlots, GetSlot, SetSlot } from './slots.mjs'; +import { ISO_MONTH, ISO_DAY, REF_ISO_YEAR, CALENDAR, CreateSlots, GetSlot, SetSlot } from './slots.mjs'; + +const ObjectAssign = Object.assign; export class MonthDay { - constructor(isoMonth, isoDay, refISOYear = 1972) { + constructor(isoMonth, isoDay, calendar = undefined, refISOYear = 1972) { isoMonth = ES.ToInteger(isoMonth); isoDay = ES.ToInteger(isoDay); + if (calendar === undefined) calendar = ES.GetDefaultCalendar(); refISOYear = ES.ToInteger(refISOYear); ES.RejectDate(refISOYear, isoMonth, isoDay); + if (!calendar || typeof calendar !== 'object') throw new RangeError('invalid calendar'); CreateSlots(this); SetSlot(this, ISO_MONTH, isoMonth); SetSlot(this, ISO_DAY, isoDay); SetSlot(this, REF_ISO_YEAR, refISOYear); + SetSlot(this, CALENDAR, calendar); } get month() { if (!ES.IsTemporalMonthDay(this)) throw new TypeError('invalid receiver'); - return GetSlot(this, ISO_MONTH); + return GetSlot(this, CALENDAR).month(this); } get day() { if (!ES.IsTemporalMonthDay(this)) throw new TypeError('invalid receiver'); - return GetSlot(this, ISO_DAY); + return GetSlot(this, CALENDAR).day(this); + } + get calendar() { + if (!ES.IsTemporalMonthDay(this)) throw new TypeError('invalid receiver'); + return GetSlot(this, CALENDAR); } with(temporalMonthDayLike, options) { if (!ES.IsTemporalMonthDay(this)) throw new TypeError('invalid receiver'); - const disambiguation = ES.ToTemporalDisambiguation(options); + if ('calendar' in temporalMonthDayLike) { + throw new RangeError('invalid calendar property in month-day-like'); + } const props = ES.ToPartialRecord(temporalMonthDayLike, ['day', 'month']); if (!props) { throw new RangeError('invalid month-day-like'); } - let { month = GetSlot(this, ISO_MONTH), day = GetSlot(this, ISO_DAY) } = props; - ({ month, day } = ES.RegulateMonthDay(month, day, disambiguation)); + const fields = ES.ToRecord(this, [['day'], ['month']]); + ObjectAssign(fields, props); const Construct = ES.SpeciesConstructor(this, MonthDay); - const result = new Construct(month, day); + const result = GetSlot(this, CALENDAR).monthDayFromFields(fields, options, Construct); if (!ES.IsTemporalMonthDay(result)) throw new TypeError('invalid result'); return result; } @@ -46,7 +57,7 @@ export class MonthDay { const val2 = GetSlot(other, slot); if (val1 !== val2) return false; } - return true; + return GetSlot(this, CALENDAR).id === GetSlot(other, CALENDAR).id; } toString() { if (!ES.IsTemporalMonthDay(this)) throw new TypeError('invalid receiver'); @@ -65,19 +76,21 @@ export class MonthDay { withYear(item) { if (!ES.IsTemporalMonthDay(this)) throw new TypeError('invalid receiver'); let year; - if (typeof item === 'object') { + if (typeof item === 'object' && item !== null) { ({ year } = ES.ToRecord(item, [['year']])); } else { year = ES.ToInteger(item); } - const month = GetSlot(this, ISO_MONTH); - const day = GetSlot(this, ISO_DAY); + const calendar = GetSlot(this, CALENDAR); + const day = calendar.day(this); + const month = calendar.month(this); const Date = GetIntrinsic('%Temporal.Date%'); - return new Date(year, month, day); + return calendar.dateFromFields({ year, month, day }, { disambiguation: 'reject' }, Date); } getFields() { const fields = ES.ToRecord(this, [['day'], ['month']]); if (!fields) throw new TypeError('invalid receiver'); + fields.calendar = GetSlot(this, CALENDAR); return fields; } getISOCalendarFields() { @@ -90,23 +103,28 @@ export class MonthDay { } static from(item, options = undefined) { const disambiguation = ES.ToTemporalDisambiguation(options); - let month, day, refISOYear; + const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%'); + let result; if (typeof item === 'object' && item) { if (ES.IsTemporalMonthDay(item)) { - month = GetSlot(item, ISO_MONTH); - day = GetSlot(item, ISO_DAY); - refISOYear = GetSlot(item, REF_ISO_YEAR); + const month = GetSlot(item, ISO_MONTH); + const day = GetSlot(item, ISO_DAY); + const calendar = GetSlot(item, CALENDAR); + const refISOYear = GetSlot(item, REF_ISO_YEAR); + result = new this(month, day, calendar, refISOYear); } else { - // Intentionally alphabetical - ({ month, day } = ES.ToRecord(item, [['day'], ['month']])); - refISOYear = 1972; + let calendar = item.calendar; + if (calendar === undefined) calendar = ES.GetDefaultCalendar(); + calendar = TemporalCalendar.from(calendar); + result = calendar.monthDayFromFields(item, options, this); } } else { - ({ month, day } = ES.ParseTemporalMonthDayString(ES.ToString(item))); - refISOYear = 1972; + let { month, day } = ES.ParseTemporalMonthDayString(ES.ToString(item)); + ({ month, day } = ES.RegulateMonthDay(month, day, disambiguation)); + const calendar = ES.GetDefaultCalendar(); + const refISOYear = 1972; + result = new this(month, day, calendar, refISOYear); } - ({ month, day } = ES.RegulateMonthDay(month, day, disambiguation)); - const result = new this(month, day, refISOYear); if (!ES.IsTemporalMonthDay(result)) throw new TypeError('invalid result'); return result; } diff --git a/polyfill/lib/slots.mjs b/polyfill/lib/slots.mjs index 0ad92e14b0..a6290ffd3e 100644 --- a/polyfill/lib/slots.mjs +++ b/polyfill/lib/slots.mjs @@ -16,6 +16,7 @@ export const MICROSECOND = 'slot-microsecond'; export const NANOSECOND = 'slot-nanosecond'; export const REF_ISO_YEAR = 'slot-ref-iso-year'; export const REF_ISO_DAY = 'slot-ref-iso-day'; +export const CALENDAR = 'slot-calendar'; // Duration export const YEARS = 'slot-years'; diff --git a/polyfill/lib/time.mjs b/polyfill/lib/time.mjs index cb7ff86cf5..abd90c4f70 100644 --- a/polyfill/lib/time.mjs +++ b/polyfill/lib/time.mjs @@ -11,6 +11,7 @@ import { MILLISECOND, MICROSECOND, NANOSECOND, + CALENDAR, CreateSlots, GetSlot, SetSlot @@ -235,8 +236,9 @@ export class Time { const millisecond = GetSlot(this, MILLISECOND); const microsecond = GetSlot(this, MICROSECOND); const nanosecond = GetSlot(this, NANOSECOND); + const calendar = GetSlot(temporalDate, CALENDAR); const DateTime = GetIntrinsic('%Temporal.DateTime%'); - return new DateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond); + return new DateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, calendar); } getFields() { const fields = ES.ToRecord(this, [ diff --git a/polyfill/lib/yearmonth.mjs b/polyfill/lib/yearmonth.mjs index 4c14c7fd7c..51002d4b98 100644 --- a/polyfill/lib/yearmonth.mjs +++ b/polyfill/lib/yearmonth.mjs @@ -1,58 +1,67 @@ import { ES } from './ecmascript.mjs'; import { GetIntrinsic, MakeIntrinsicClass } from './intrinsicclass.mjs'; -import { ISO_YEAR, ISO_MONTH, REF_ISO_DAY, CreateSlots, GetSlot, SetSlot } from './slots.mjs'; +import { ISO_YEAR, ISO_MONTH, REF_ISO_DAY, CALENDAR, CreateSlots, GetSlot, SetSlot } from './slots.mjs'; + +const ObjectAssign = Object.assign; export class YearMonth { - constructor(isoYear, isoMonth, refISODay = 1) { + constructor(isoYear, isoMonth, calendar = undefined, refISODay = 1) { isoYear = ES.ToInteger(isoYear); isoMonth = ES.ToInteger(isoMonth); + if (calendar === undefined) calendar = ES.GetDefaultCalendar(); refISODay = ES.ToInteger(refISODay); ES.RejectYearMonth(isoYear, isoMonth, refISODay); + if (!calendar || typeof calendar !== 'object') throw new RangeError('invalid calendar'); CreateSlots(this); SetSlot(this, ISO_YEAR, isoYear); SetSlot(this, ISO_MONTH, isoMonth); SetSlot(this, REF_ISO_DAY, refISODay); + SetSlot(this, CALENDAR, calendar); } get year() { if (!ES.IsTemporalYearMonth(this)) throw new TypeError('invalid receiver'); - return GetSlot(this, ISO_YEAR); + return GetSlot(this, CALENDAR).year(this); } get month() { if (!ES.IsTemporalYearMonth(this)) throw new TypeError('invalid receiver'); - return GetSlot(this, ISO_MONTH); + return GetSlot(this, CALENDAR).month(this); + } + get calendar() { + if (!ES.IsTemporalYearMonth(this)) throw new TypeError('invalid receiver'); + return GetSlot(this, CALENDAR); } get daysInMonth() { if (!ES.IsTemporalYearMonth(this)) throw new TypeError('invalid receiver'); - return ES.DaysInMonth(GetSlot(this, ISO_YEAR), GetSlot(this, ISO_MONTH)); + return GetSlot(this, CALENDAR).daysInMonth(this); } get daysInYear() { if (!ES.IsTemporalYearMonth(this)) throw new TypeError('invalid receiver'); - return ES.LeapYear(GetSlot(this, ISO_YEAR)) ? 366 : 365; + return GetSlot(this, CALENDAR).daysInYear(this); } get isLeapYear() { if (!ES.IsTemporalYearMonth(this)) throw new TypeError('invalid receiver'); - return ES.LeapYear(GetSlot(this, ISO_YEAR)); + return GetSlot(this, CALENDAR).isLeapYear(this); } with(temporalYearMonthLike = {}, options) { if (!ES.IsTemporalYearMonth(this)) throw new TypeError('invalid receiver'); - const disambiguation = ES.ToTemporalDisambiguation(options); + if ('calendar' in temporalYearMonthLike) { + throw new RangeError('invalid calendar property in year-month-like'); + } const props = ES.ToPartialRecord(temporalYearMonthLike, ['month', 'year']); if (!props) { throw new RangeError('invalid year-month-like'); } - let { year = GetSlot(this, ISO_YEAR), month = GetSlot(this, ISO_MONTH) } = props; - ({ year, month } = ES.RegulateYearMonth(year, month, disambiguation)); + const fields = ES.ToRecord(this, [['month'], ['year']]); + ObjectAssign(fields, props); const Construct = ES.SpeciesConstructor(this, YearMonth); - const result = new Construct(year, month); + const result = GetSlot(this, CALENDAR).yearMonthFromFields(fields, options, Construct); if (!ES.IsTemporalYearMonth(result)) throw new TypeError('invalid result'); return result; } plus(temporalDurationLike, options) { if (!ES.IsTemporalYearMonth(this)) throw new TypeError('invalid receiver'); - const disambiguation = ES.ToTemporalDisambiguation(options); const duration = ES.ToLimitedTemporalDuration(temporalDurationLike); - let { year, month } = this; - const { years, months, weeks, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = duration; + const { hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = duration; const { days } = ES.BalanceDuration( duration.days, hours, @@ -63,20 +72,23 @@ export class YearMonth { nanoseconds, 'days' ); - ({ year, month } = ES.AddDate(year, month, 1, years, months, weeks, days, disambiguation)); - ({ year, month } = ES.BalanceYearMonth(year, month)); - ({ year, month } = ES.RegulateYearMonth(year, month, disambiguation)); + + const Date = GetIntrinsic('%Temporal.Date%'); + const calendar = GetSlot(this, CALENDAR); + const year = calendar.year(this); + const month = calendar.month(this); + const firstOfCalendarMonth = calendar.dateFromFields({ year, month, day: 1 }, {}, Date); + const addedDate = calendar.plus(firstOfCalendarMonth, { ...duration, days }, options, Date); + const Construct = ES.SpeciesConstructor(this, YearMonth); - const result = new Construct(year, month); + const result = calendar.yearMonthFromFields(addedDate, options, Construct); if (!ES.IsTemporalYearMonth(result)) throw new TypeError('invalid result'); return result; } minus(temporalDurationLike, options) { if (!ES.IsTemporalYearMonth(this)) throw new TypeError('invalid receiver'); - const disambiguation = ES.ToTemporalDisambiguation(options); const duration = ES.ToLimitedTemporalDuration(temporalDurationLike); - let { year, month } = this; - const { years, months, weeks, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = duration; + const { hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = duration; const { days } = ES.BalanceDuration( duration.days, hours, @@ -87,32 +99,33 @@ export class YearMonth { nanoseconds, 'days' ); - const lastDay = ES.DaysInMonth(year, month); - ({ year, month } = ES.SubtractDate(year, month, lastDay, years, months, weeks, days, disambiguation)); - ({ year, month } = ES.BalanceYearMonth(year, month)); - ({ year, month } = ES.RegulateYearMonth(year, month, disambiguation)); + + const Date = GetIntrinsic('%Temporal.Date%'); + const calendar = GetSlot(this, CALENDAR); + const year = calendar.year(this); + const month = calendar.month(this); + const lastDay = calendar.daysInMonth(this); + const lastOfCalendarMonth = calendar.dateFromFields({ year, month, day: lastDay }, {}, Date); + const subtractedDate = calendar.minus(lastOfCalendarMonth, { ...duration, days }, options, Date); + const Construct = ES.SpeciesConstructor(this, YearMonth); - const result = new Construct(year, month); + const result = calendar.yearMonthFromFields(subtractedDate, options, Construct); if (!ES.IsTemporalYearMonth(result)) throw new TypeError('invalid result'); return result; } difference(other, options) { if (!ES.IsTemporalYearMonth(this)) throw new TypeError('invalid receiver'); if (!ES.IsTemporalYearMonth(other)) throw new TypeError('invalid YearMonth object'); + const calendar = GetSlot(this, CALENDAR); + if (calendar.id !== GetSlot(other, CALENDAR).id) { + other = new Date(GetSlot(other, ISO_YEAR), GetSlot(other, ISO_MONTH), calendar, GetSlot(other, REF_ISO_DAY)); + } const largestUnit = ES.ToLargestTemporalUnit(options, 'years', ['weeks', 'days', 'hours', 'minutes', 'seconds']); const [one, two] = [this, other].sort(YearMonth.compare); - let years = two.year - one.year; - let months = two.month - one.month; - if (months < 0) { - years -= 1; - months += 12; - } - if (largestUnit === 'months') { - months += 12 * years; - years = 0; - } - const Duration = GetIntrinsic('%Temporal.Duration%'); - return new Duration(years, months); + + const smaller = { year: one.year, month: one.month, day: 1 }; + const larger = { year: two.year, month: two.month, day: 1 }; + return calendar.difference(smaller, larger, { ...options, largestUnit }); } equals(other) { if (!ES.IsTemporalYearMonth(this)) throw new TypeError('invalid receiver'); @@ -122,7 +135,7 @@ export class YearMonth { const val2 = GetSlot(other, slot); if (val1 !== val2) return false; } - return true; + return GetSlot(this, CALENDAR).id === GetSlot(other, CALENDAR).id; } toString() { if (!ES.IsTemporalYearMonth(this)) throw new TypeError('invalid receiver'); @@ -140,14 +153,16 @@ export class YearMonth { } withDay(day) { if (!ES.IsTemporalYearMonth(this)) throw new TypeError('invalid receiver'); - const year = GetSlot(this, ISO_YEAR); - const month = GetSlot(this, ISO_MONTH); + const calendar = GetSlot(this, CALENDAR); + const month = calendar.month(this); + const year = calendar.year(this); const Date = GetIntrinsic('%Temporal.Date%'); - return new Date(year, month, day); + return calendar.dateFromFields({ year, month, day }, { disambiguation: 'reject' }, Date); } getFields() { const fields = ES.ToRecord(this, [['month'], ['year']]); if (!fields) throw new TypeError('invalid receiver'); + fields.calendar = GetSlot(this, CALENDAR); return fields; } getISOCalendarFields() { @@ -160,23 +175,28 @@ export class YearMonth { } static from(item, options = undefined) { const disambiguation = ES.ToTemporalDisambiguation(options); - let year, month, refISODay; + const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%'); + let result; if (typeof item === 'object' && item) { if (ES.IsTemporalYearMonth(item)) { - year = GetSlot(item, ISO_YEAR); - month = GetSlot(item, ISO_MONTH); - refISODay = GetSlot(item, REF_ISO_DAY); + const year = GetSlot(item, ISO_YEAR); + const month = GetSlot(item, ISO_MONTH); + const calendar = GetSlot(item, CALENDAR); + const refISODay = GetSlot(item, REF_ISO_DAY); + result = new this(year, month, calendar, refISODay); } else { - // Intentionally alphabetical - ({ year, month } = ES.ToRecord(item, [['month'], ['year']])); - refISODay = 1; + let calendar = item.calendar; + if (calendar === undefined) calendar = ES.GetDefaultCalendar(); + calendar = TemporalCalendar.from(calendar); + result = calendar.yearMonthFromFields(item, options, this); } } else { - ({ year, month } = ES.ParseTemporalYearMonthString(ES.ToString(item))); - refISODay = 1; + let { year, month, refISODay, calendar } = ES.ParseTemporalYearMonthString(ES.ToString(item)); + ({ year, month } = ES.RegulateYearMonth(year, month, disambiguation)); + if (!calendar) calendar = ES.GetDefaultCalendar(); + if (refISODay === undefined) refISODay = 1; + result = new this(year, month, calendar, refISODay); } - ({ year, month } = ES.RegulateYearMonth(year, month, disambiguation)); - const result = new this(year, month, refISODay); if (!ES.IsTemporalYearMonth(result)) throw new TypeError('invalid result'); return result; } @@ -187,7 +207,9 @@ export class YearMonth { const val2 = GetSlot(two, slot); if (val1 !== val2) return ES.ComparisonResult(val1 - val2); } - return ES.ComparisonResult(0); + const cal1 = GetSlot(one, CALENDAR).id; + const cal2 = GetSlot(two, CALENDAR).id; + return ES.ComparisonResult(cal1 < cal2 ? -1 : cal1 > cal2 ? 1 : 0); } } YearMonth.prototype.toJSON = YearMonth.prototype.toString; diff --git a/polyfill/test/Date/constructor/from/order-of-operations.js b/polyfill/test/Date/constructor/from/order-of-operations.js index 15815595da..c843d85c66 100644 --- a/polyfill/test/Date/constructor/from/order-of-operations.js +++ b/polyfill/test/Date/constructor/from/order-of-operations.js @@ -7,6 +7,8 @@ includes: [compareArray.js] ---*/ const expected = [ + "get calendar", + "toString calendar", "get day", "valueOf day", "get month", @@ -19,6 +21,7 @@ const fields = { year: 1.7, month: 1.7, day: 1.7, + calendar: "iso8601", }; const argument = new Proxy(fields, { get(target, key) { @@ -28,6 +31,10 @@ const argument = new Proxy(fields, { valueOf() { actual.push(`valueOf ${key}`); return result; + }, + toString() { + actual.push(`toString ${key}`); + return result.toString(); } }; }, @@ -40,4 +47,5 @@ const result = Temporal.Date.from(argument); assert.sameValue(result.year, 1, "year result"); assert.sameValue(result.month, 1, "month result"); assert.sameValue(result.day, 1, "day result"); +assert.sameValue(result.calendar.id, "iso8601", "calendar result"); assert.compareArray(actual, expected, "order of operations"); diff --git a/polyfill/test/Date/prototype/with/order-of-operations.js b/polyfill/test/Date/prototype/with/order-of-operations.js index 314e65aa95..2650c66414 100644 --- a/polyfill/test/Date/prototype/with/order-of-operations.js +++ b/polyfill/test/Date/prototype/with/order-of-operations.js @@ -8,6 +8,8 @@ includes: [compareArray.js] const instance = new Temporal.Date(2000, 5, 2); const expected = [ + "get calendar", + "toString calendar", "get day", "valueOf day", "get month", @@ -20,6 +22,7 @@ const fields = { year: 1.7, month: 1.7, day: 1.7, + calendar: "iso8601", }; const argument = new Proxy(fields, { get(target, key) { @@ -32,6 +35,10 @@ const argument = new Proxy(fields, { valueOf() { actual.push(`valueOf ${key}`); return result; + }, + toString() { + actual.push(`toString ${key}`); + return result.toString(); } }; }, @@ -44,4 +51,5 @@ const result = instance.with(argument); assert.sameValue(result.year, 1, "year result"); assert.sameValue(result.month, 1, "month result"); assert.sameValue(result.day, 1, "day result"); +assert.sameValue(result.calendar.id, "iso8601", "calendar result"); assert.compareArray(actual, expected, "order of operations"); diff --git a/polyfill/test/DateTime/constructor/from/order-of-operations.js b/polyfill/test/DateTime/constructor/from/order-of-operations.js index ecbca2022c..129da83eae 100644 --- a/polyfill/test/DateTime/constructor/from/order-of-operations.js +++ b/polyfill/test/DateTime/constructor/from/order-of-operations.js @@ -7,6 +7,8 @@ includes: [compareArray.js] ---*/ const expected = [ + "get calendar", + "toString calendar", "get day", "valueOf day", "get hour", @@ -37,6 +39,7 @@ const fields = { millisecond: 1.7, microsecond: 1.7, nanosecond: 1.7, + calendar: "iso8601", }; const argument = new Proxy(fields, { get(target, key) { @@ -46,6 +49,10 @@ const argument = new Proxy(fields, { valueOf() { actual.push(`valueOf ${key}`); return result; + }, + toString() { + actual.push(`toString ${key}`); + return result.toString(); } }; }, @@ -64,4 +71,5 @@ assert.sameValue(result.second, 1, "second result"); assert.sameValue(result.millisecond, 1, "millisecond result"); assert.sameValue(result.microsecond, 1, "microsecond result"); assert.sameValue(result.nanosecond, 1, "nanosecond result"); +assert.sameValue(result.calendar.id, "iso8601", "calendar result"); assert.compareArray(actual, expected, "order of operations"); diff --git a/polyfill/test/DateTime/prototype/with/order-of-operations.js b/polyfill/test/DateTime/prototype/with/order-of-operations.js index 127032eff9..36c40c6235 100644 --- a/polyfill/test/DateTime/prototype/with/order-of-operations.js +++ b/polyfill/test/DateTime/prototype/with/order-of-operations.js @@ -8,6 +8,8 @@ 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", @@ -38,6 +40,7 @@ const fields = { millisecond: 1.7, microsecond: 1.7, nanosecond: 1.7, + calendar: "iso8601", }; const argument = new Proxy(fields, { get(target, key) { @@ -50,6 +53,10 @@ const argument = new Proxy(fields, { valueOf() { actual.push(`valueOf ${key}`); return result; + }, + toString() { + actual.push(`toString ${key}`); + return result.toString(); } }; }, @@ -68,4 +75,5 @@ assert.sameValue(result.second, 1, "second result"); assert.sameValue(result.millisecond, 1, "millisecond result"); assert.sameValue(result.microsecond, 1, "microsecond result"); assert.sameValue(result.nanosecond, 1, "nanosecond result"); +assert.sameValue(result.calendar.id, "iso8601", "calendar result"); assert.compareArray(actual, expected, "order of operations"); diff --git a/polyfill/test/MonthDay/constructor/from/order-of-operations.js b/polyfill/test/MonthDay/constructor/from/order-of-operations.js index 1a30f6f02e..990af90ec2 100644 --- a/polyfill/test/MonthDay/constructor/from/order-of-operations.js +++ b/polyfill/test/MonthDay/constructor/from/order-of-operations.js @@ -7,6 +7,8 @@ includes: [compareArray.js] ---*/ const expected = [ + "get calendar", + "toString calendar", "get day", "valueOf day", "get month", @@ -16,6 +18,7 @@ const actual = []; const fields = { month: 1.7, day: 1.7, + calendar: "iso8601", }; const argument = new Proxy(fields, { get(target, key) { @@ -25,6 +28,10 @@ const argument = new Proxy(fields, { valueOf() { actual.push(`valueOf ${key}`); return result; + }, + toString() { + actual.push(`toString ${key}`); + return result.toString(); } }; }, @@ -36,4 +43,5 @@ const argument = new Proxy(fields, { const result = Temporal.MonthDay.from(argument); assert.sameValue(result.month, 1, "month result"); assert.sameValue(result.day, 1, "day result"); +assert.sameValue(result.calendar.id, "iso8601", "calendar result"); assert.compareArray(actual, expected, "order of operations"); diff --git a/polyfill/test/MonthDay/constructor/infinity-throws-rangeerror.js b/polyfill/test/MonthDay/constructor/infinity-throws-rangeerror.js index 94aa46c903..99e3181373 100644 --- a/polyfill/test/MonthDay/constructor/infinity-throws-rangeerror.js +++ b/polyfill/test/MonthDay/constructor/infinity-throws-rangeerror.js @@ -2,13 +2,15 @@ // This code is governed by the BSD license found in the LICENSE file. /*--- -description: Temporal.MonthDay throws a RangeError if any value is Infinity +description: Temporal.MonthDay throws a RangeError if any numerical value is Infinity esid: sec-temporal.monthday ---*/ +const isoCalendar = Temporal.Calendar.from('iso8601'); + assert.throws(RangeError, () => new Temporal.MonthDay(Infinity, 1)); assert.throws(RangeError, () => new Temporal.MonthDay(1, Infinity)); -assert.throws(RangeError, () => new Temporal.MonthDay(1, 1, Infinity)); +assert.throws(RangeError, () => new Temporal.MonthDay(1, 1, isoCalendar, Infinity)); let calls = 0; const obj = { @@ -22,5 +24,5 @@ assert.throws(RangeError, () => new Temporal.MonthDay(obj, 1)); assert.sameValue(calls, 1, "it fails after fetching the primitive value"); assert.throws(RangeError, () => new Temporal.MonthDay(1, obj)); assert.sameValue(calls, 2, "it fails after fetching the primitive value"); -assert.throws(RangeError, () => new Temporal.MonthDay(1, 1, obj)); +assert.throws(RangeError, () => new Temporal.MonthDay(1, 1, isoCalendar, obj)); assert.sameValue(calls, 3, "it fails after fetching the primitive value"); diff --git a/polyfill/test/MonthDay/constructor/negative-infinity-throws-rangeerror.js b/polyfill/test/MonthDay/constructor/negative-infinity-throws-rangeerror.js index 40a91b4e8e..c7032a9096 100644 --- a/polyfill/test/MonthDay/constructor/negative-infinity-throws-rangeerror.js +++ b/polyfill/test/MonthDay/constructor/negative-infinity-throws-rangeerror.js @@ -2,13 +2,15 @@ // This code is governed by the BSD license found in the LICENSE file. /*--- -description: Temporal.MonthDay throws a RangeError if any value is -Infinity +description: Temporal.MonthDay throws a RangeError if any numerical value is -Infinity esid: sec-temporal.monthday ---*/ +const isoCalendar = Temporal.Calendar.from('iso8601'); + assert.throws(RangeError, () => new Temporal.MonthDay(-Infinity, 1)); assert.throws(RangeError, () => new Temporal.MonthDay(1, -Infinity)); -assert.throws(RangeError, () => new Temporal.MonthDay(1, 1, -Infinity)); +assert.throws(RangeError, () => new Temporal.MonthDay(1, 1, isoCalendar, -Infinity)); let calls = 0; const obj = { @@ -22,5 +24,5 @@ assert.throws(RangeError, () => new Temporal.MonthDay(obj, 1)); assert.sameValue(calls, 1, "it fails after fetching the primitive value"); assert.throws(RangeError, () => new Temporal.MonthDay(1, obj)); assert.sameValue(calls, 2, "it fails after fetching the primitive value"); -assert.throws(RangeError, () => new Temporal.MonthDay(1, 1, obj)); +assert.throws(RangeError, () => new Temporal.MonthDay(1, 1, isoCalendar, obj)); assert.sameValue(calls, 3, "it fails after fetching the primitive value"); diff --git a/polyfill/test/MonthDay/prototype/with/order-of-operations.js b/polyfill/test/MonthDay/prototype/with/order-of-operations.js index 8a4b9edad5..2a03a994ec 100644 --- a/polyfill/test/MonthDay/prototype/with/order-of-operations.js +++ b/polyfill/test/MonthDay/prototype/with/order-of-operations.js @@ -8,6 +8,7 @@ includes: [compareArray.js] const instance = new Temporal.MonthDay(5, 2); const expected = [ + "has calendar", "get day", "valueOf day", "get month", diff --git a/polyfill/test/YearMonth/constructor/from/order-of-operations.js b/polyfill/test/YearMonth/constructor/from/order-of-operations.js index 7a1386f2a5..08c6e01f38 100644 --- a/polyfill/test/YearMonth/constructor/from/order-of-operations.js +++ b/polyfill/test/YearMonth/constructor/from/order-of-operations.js @@ -7,6 +7,8 @@ includes: [compareArray.js] ---*/ const expected = [ + "get calendar", + "toString calendar", "get month", "valueOf month", "get year", @@ -16,6 +18,7 @@ const actual = []; const fields = { year: 1.7, month: 1.7, + calendar: "iso8601", }; const argument = new Proxy(fields, { get(target, key) { @@ -25,6 +28,10 @@ const argument = new Proxy(fields, { valueOf() { actual.push(`valueOf ${key}`); return result; + }, + toString() { + actual.push(`toString ${key}`); + return result.toString(); } }; }, @@ -36,4 +43,5 @@ const argument = new Proxy(fields, { const result = Temporal.YearMonth.from(argument); assert.sameValue(result.year, 1, "year result"); assert.sameValue(result.month, 1, "month result"); +assert.sameValue(result.calendar.id, "iso8601", "calendar result"); assert.compareArray(actual, expected, "order of operations"); diff --git a/polyfill/test/YearMonth/constructor/infinity-throws-rangeerror.js b/polyfill/test/YearMonth/constructor/infinity-throws-rangeerror.js index e53dba772a..a51db4dbfe 100644 --- a/polyfill/test/YearMonth/constructor/infinity-throws-rangeerror.js +++ b/polyfill/test/YearMonth/constructor/infinity-throws-rangeerror.js @@ -2,13 +2,15 @@ // This code is governed by the BSD license found in the LICENSE file. /*--- -description: Temporal.YearMonth throws a RangeError if any value is Infinity +description: Temporal.YearMonth throws a RangeError if any numerical value is Infinity esid: sec-temporal.yearmonth ---*/ +const isoCalendar = Temporal.Calendar.from('iso8601'); + assert.throws(RangeError, () => new Temporal.YearMonth(Infinity, 1)); assert.throws(RangeError, () => new Temporal.YearMonth(1970, Infinity)); -assert.throws(RangeError, () => new Temporal.YearMonth(1970, 1, Infinity)); +assert.throws(RangeError, () => new Temporal.YearMonth(1970, 1, isoCalendar, Infinity)); let calls = 0; const obj = { @@ -22,5 +24,5 @@ assert.throws(RangeError, () => new Temporal.YearMonth(obj, 1)); assert.sameValue(calls, 1, "it fails after fetching the primitive value"); assert.throws(RangeError, () => new Temporal.YearMonth(1970, obj)); assert.sameValue(calls, 2, "it fails after fetching the primitive value"); -assert.throws(RangeError, () => new Temporal.YearMonth(1970, 1, obj)); +assert.throws(RangeError, () => new Temporal.YearMonth(1970, 1, isoCalendar, obj)); assert.sameValue(calls, 3, "it fails after fetching the primitive value"); diff --git a/polyfill/test/YearMonth/constructor/negative-infinity-throws-rangeerror.js b/polyfill/test/YearMonth/constructor/negative-infinity-throws-rangeerror.js index 4546ccae82..1bc0dba405 100644 --- a/polyfill/test/YearMonth/constructor/negative-infinity-throws-rangeerror.js +++ b/polyfill/test/YearMonth/constructor/negative-infinity-throws-rangeerror.js @@ -2,13 +2,15 @@ // This code is governed by the BSD license found in the LICENSE file. /*--- -description: Temporal.YearMonth throws a RangeError if any value is -Infinity +description: Temporal.YearMonth throws a RangeError if any numerical value is -Infinity esid: sec-temporal.yearmonth ---*/ +const isoCalendar = Temporal.Calendar.from('iso8601'); + assert.throws(RangeError, () => new Temporal.YearMonth(-Infinity, 1)); assert.throws(RangeError, () => new Temporal.YearMonth(1970, -Infinity)); -assert.throws(RangeError, () => new Temporal.YearMonth(1970, 1, -Infinity)); +assert.throws(RangeError, () => new Temporal.YearMonth(1970, 1, isoCalendar, -Infinity)); let calls = 0; const obj = { @@ -22,5 +24,5 @@ assert.throws(RangeError, () => new Temporal.YearMonth(obj, 1)); assert.sameValue(calls, 1, "it fails after fetching the primitive value"); assert.throws(RangeError, () => new Temporal.YearMonth(1970, obj)); assert.sameValue(calls, 2, "it fails after fetching the primitive value"); -assert.throws(RangeError, () => new Temporal.YearMonth(1970, 1, obj)); +assert.throws(RangeError, () => new Temporal.YearMonth(1970, 1, isoCalendar, obj)); assert.sameValue(calls, 3, "it fails after fetching the primitive value"); diff --git a/polyfill/test/YearMonth/prototype/with/order-of-operations.js b/polyfill/test/YearMonth/prototype/with/order-of-operations.js index ffef6e8bb6..8f414cc90c 100644 --- a/polyfill/test/YearMonth/prototype/with/order-of-operations.js +++ b/polyfill/test/YearMonth/prototype/with/order-of-operations.js @@ -8,6 +8,7 @@ includes: [compareArray.js] const instance = new Temporal.YearMonth(2000, 5); const expected = [ + "has calendar", "get month", "valueOf month", "get year", diff --git a/polyfill/test/date.mjs b/polyfill/test/date.mjs index 520fe7ce50..85a27b71f9 100644 --- a/polyfill/test/date.mjs +++ b/polyfill/test/date.mjs @@ -91,14 +91,16 @@ describe('Date', () => { }); describe('Construction', () => { let date; + const calendar = Temporal.Calendar.from('iso8601'); it('date can be constructed', () => { - date = new Date(1976, 11, 18); + date = new Date(1976, 11, 18, calendar); assert(date); equal(typeof date, 'object'); }); it('date.year is 1976', () => equal(date.year, 1976)); it('date.month is 11', () => equal(date.month, 11)); it('date.day is 18', () => equal(date.day, 18)); + it('date.calendar is the object', () => equal(date.calendar, calendar)); it('date.dayOfWeek is 4', () => equal(date.dayOfWeek, 4)); it('date.dayOfYear is 323', () => equal(date.dayOfYear, 323)); it('date.weekOfYear is 47', () => equal(date.weekOfYear, 47)); @@ -525,18 +527,21 @@ describe('Date', () => { }); }); describe('date.getFields() works', () => { - const d1 = Date.from('1976-11-18'); + const calendar = Temporal.Calendar.from('iso8601'); + const d1 = Date.from({ year: 1976, month: 11, day: 18, calendar }); const fields = d1.getFields(); it('fields', () => { equal(fields.year, 1976); equal(fields.month, 11); equal(fields.day, 18); + equal(fields.calendar, calendar); }); it('enumerable', () => { const fields2 = { ...fields }; equal(fields2.year, 1976); equal(fields2.month, 11); equal(fields2.day, 18); + equal(fields2.calendar, calendar); }); it('as input to from()', () => { const d2 = Date.from(fields); diff --git a/polyfill/test/datetime.mjs b/polyfill/test/datetime.mjs index edb389c3a7..47fcb44fb6 100644 --- a/polyfill/test/datetime.mjs +++ b/polyfill/test/datetime.mjs @@ -108,10 +108,11 @@ describe('DateTime', () => { }); }); describe('Construction', () => { - describe('new DateTime(1976, 11, 18, 15, 23, 30, 123, 456, 789)', () => { + describe('new DateTime(1976, 11, 18, 15, 23, 30, 123, 456, 789, calendar)', () => { let datetime; + const calendar = Temporal.Calendar.from('iso8601'); it('datetime can be constructed', () => { - datetime = new DateTime(1976, 11, 18, 15, 23, 30, 123, 456, 789); + datetime = new DateTime(1976, 11, 18, 15, 23, 30, 123, 456, 789, calendar); assert(datetime); equal(typeof datetime, 'object'); }); @@ -124,6 +125,7 @@ describe('DateTime', () => { it('datetime.millisecond is 123', () => equal(datetime.millisecond, 123)); it('datetime.microsecond is 456', () => equal(datetime.microsecond, 456)); it('datetime.nanosecond is 789', () => equal(datetime.nanosecond, 789)); + it('datetime.calendar is the object', () => equal(datetime.calendar, calendar)); it('datetime.dayOfWeek is 4', () => equal(datetime.dayOfWeek, 4)); it('datetime.dayOfYear is 323', () => equal(datetime.dayOfYear, 323)); it('datetime.weekOfYear is 47', () => equal(datetime.weekOfYear, 47)); @@ -589,7 +591,19 @@ describe('DateTime', () => { }); }); describe('dateTime.getFields() works', () => { - const dt1 = DateTime.from('1976-11-18T15:23:30.123456789'); + const calendar = Temporal.Calendar.from('iso8601'); + const dt1 = DateTime.from({ + year: 1976, + month: 11, + day: 18, + hour: 15, + minute: 23, + second: 30, + millisecond: 123, + microsecond: 456, + nanosecond: 789, + calendar + }); const fields = dt1.getFields(); it('fields', () => { equal(fields.year, 1976); @@ -601,6 +615,7 @@ describe('DateTime', () => { equal(fields.millisecond, 123); equal(fields.microsecond, 456); equal(fields.nanosecond, 789); + equal(fields.calendar, calendar); }); it('enumerable', () => { const fields2 = { ...fields }; @@ -613,6 +628,7 @@ describe('DateTime', () => { equal(fields2.millisecond, 123); equal(fields2.microsecond, 456); equal(fields2.nanosecond, 789); + equal(fields2.calendar, calendar); }); it('as input to from()', () => { const dt2 = DateTime.from(fields); diff --git a/polyfill/test/monthday.mjs b/polyfill/test/monthday.mjs index f03e7c92f0..39113c846b 100644 --- a/polyfill/test/monthday.mjs +++ b/polyfill/test/monthday.mjs @@ -123,6 +123,9 @@ describe('MonthDay', () => { throws(() => MonthDay.from('01-15').with({ day: 1 }, { disambiguation }), RangeError) ); }); + it('throws on trying to change the calendar', () => { + throws(() => MonthDay.from('01-15').with({ calendar: 'gregory' }), RangeError); + }); }); describe('MonthDay.equals()', () => { const md1 = MonthDay.from('01-22'); @@ -134,8 +137,9 @@ describe('MonthDay', () => { throws(() => md1.equals({ month: 1, day: 22 }), TypeError); }); it('takes [[RefISOYear]] into account', () => { - const md1 = new MonthDay(1, 1, 1972); - const md2 = new MonthDay(1, 1, 2000); + const iso = Temporal.Calendar.from('iso8601'); + const md1 = new MonthDay(1, 1, iso, 1972); + const md2 = new MonthDay(1, 1, iso, 2000); assert(!md1.equals(md2)); }); }); @@ -167,25 +171,24 @@ describe('MonthDay', () => { }); }); describe('monthDay.getFields() works', () => { - const md1 = MonthDay.from('11-18'); + const calendar = Temporal.Calendar.from('iso8601'); + const md1 = MonthDay.from({ month: 11, day: 18, calendar }); const fields = md1.getFields(); it('fields', () => { equal(fields.month, 11); equal(fields.day, 18); + equal(fields.calendar, calendar); }); it('enumerable', () => { const fields2 = { ...fields }; equal(fields2.month, 11); equal(fields2.day, 18); + equal(fields2.calendar, calendar); }); it('as input to from()', () => { const md2 = MonthDay.from(fields); assert(md1.equals(md2)); }); - it('as input to with()', () => { - const md2 = MonthDay.from('06-30').with(fields); - assert(md1.equals(md2)); - }); }); describe('monthDay.getISOCalendarFields() works', () => { const md1 = MonthDay.from('11-18'); diff --git a/polyfill/test/yearmonth.mjs b/polyfill/test/yearmonth.mjs index 705f9b6a55..8d8945ab8e 100644 --- a/polyfill/test/yearmonth.mjs +++ b/polyfill/test/yearmonth.mjs @@ -118,6 +118,11 @@ describe('YearMonth', () => { it('with(09)', () => equal(`${ym.with({ month: 9 })}`, '2019-09')); }); }); + describe('YearMonth.with() works', () => { + it('throws on trying to change the calendar', () => { + throws(() => YearMonth.from('2019-10').with({ calendar: 'gregory' }), RangeError); + }); + }); describe('YearMonth.compare() works', () => { const nov94 = YearMonth.from('1994-11'); const jun13 = YearMonth.from('2013-06'); @@ -133,8 +138,9 @@ describe('YearMonth', () => { throws(() => YearMonth.compare(nov94, '2013-06'), TypeError); }); it('takes [[RefISODay]] into account', () => { - const ym1 = new YearMonth(2000, 1, 1); - const ym2 = new YearMonth(2000, 1, 2); + const iso = Temporal.Calendar.from('iso8601'); + const ym1 = new YearMonth(2000, 1, iso, 1); + const ym2 = new YearMonth(2000, 1, iso, 2); equal(YearMonth.compare(ym1, ym2), -1); }); }); @@ -148,8 +154,9 @@ describe('YearMonth', () => { throws(() => nov94.equals('1994-11'), TypeError); }); it('takes [[RefISODay]] into account', () => { - const ym1 = new YearMonth(2000, 1, 1); - const ym2 = new YearMonth(2000, 1, 2); + const iso = Temporal.Calendar.from('iso8601'); + const ym1 = new YearMonth(2000, 1, iso, 1); + const ym2 = new YearMonth(2000, 1, iso, 2); assert(!ym1.equals(ym2)); }); }); @@ -348,25 +355,24 @@ describe('YearMonth', () => { }); }); describe('yearMonth.getFields() works', () => { - const ym1 = YearMonth.from('1976-11'); + const calendar = Temporal.Calendar.from('iso8601'); + const ym1 = YearMonth.from({ year: 1976, month: 11, calendar }); const fields = ym1.getFields(); it('fields', () => { equal(fields.year, 1976); equal(fields.month, 11); + equal(fields.calendar, calendar); }); it('enumerable', () => { const fields2 = { ...fields }; equal(fields2.year, 1976); equal(fields2.month, 11); + equal(fields2.calendar, calendar); }); it('as input to from()', () => { const ym2 = YearMonth.from(fields); equal(YearMonth.compare(ym1, ym2), 0); }); - it('as input to with()', () => { - const ym2 = YearMonth.from('2019-06').with(fields); - equal(YearMonth.compare(ym1, ym2), 0); - }); }); describe('yearMonth.getISOCalendarFields() works', () => { const ym1 = YearMonth.from('1976-11');