It's clear what Temporal.Date{,Time}.prototype.withCalendar() does: create a new instance with the same ISO fields, and a different calendar, projecting the old instance's date into the new calendar. Note that YearMonth and MonthDay don't have withCalendar().
@sffc and I had a conversation some time ago about what date.with({...someFields, calendar}) should do. Should it throw? Should it discard the calendar property? Should it interpret the existing and passed-in fields as being in the old or the new calendar space? We did come to a conclusion but I'm having second thoughts about it because I think there are just too many possible outcomes, each of which is probably not what the programmer meant.
For some examples, let's take date as being today's date in the hebrew calendar.
Temporal.Calendar.from('hebrew').year: 5780.month: 9.day: 9Option 1, throw:
date.with({ year: 2021, calendar: 'iso8601' }) โ throwsdate.with({ calendar: 'iso8601' }) โ throwswith() no longer works, because they have calendar properties: date.with(new Temporal.YearMonth(2021, 6)) throws tooOption 2, discard:
date.with({ year: 2021, calendar: 'iso8601' }) โ Hebrew date 2021-09-09 a.k.a. -001739-05-13[c=hebrew]date.with({ calendar: 'iso8601' }) โ same as datedate.with({ year: 2021, age: 'aquarius' })Option 3, interpret existing and passed-in fields in the old calendar space:
date.with({ year: 2021, calendar: 'iso8601' }) โ -001739-05-13 a.k.a. the Hebrew date 2021-09-09 but in the ISO calendardate.with({ calendar: 'iso8601' }) โ same as date.withCalendar('iso8601')date.with({calendar}) and date.withCalendar(calendar) behave the same, which is probably what you wantedcalendar, almost certainly not what you wantedOption 4, interpret existing and passed-in fields in the new calendar space:
date.with({ year: 2021, calendar: 'iso8601' }) โ 2021-09-09date.with({ calendar: 'iso8601' }) โ 5780-09-09Option 5, interpret existing fields in the old calendar space, passed-in fields in the new calendar space:
date.with({ year: 2021, calendar: 'iso8601' }) โ 2021-06-01date.with({ calendar: 'iso8601' }) โ 2020-06-01Temporal.MonthDay.from({month: 1, day: 1, calendar: 'hebrew' }).with({calendar: 'iso8601'}) โ 09-25Option 6, interpret existing fields in the new calendar space, passed-in fields in the old calendar space:
I believe what @sffc and I landed on last time was option 5, but I'm starting to lean away from that because I realized you either have to stick with option 1 (throwing) for YearMonth and MonthDay anyway and be inconsistent, or you have to accept that users can do a (faulty!) calendar conversion using with() where we didn't intend that to be possible.
For the initial version of the polyfill I intend to stick with option 5 and just disallow calendar properties for YearMonth and MonthDay for the time being. Let me know soon if you disagree with that as a provisional choice, otherwise we can revisit this with feedback.
Option 7: what you said at the bottom. Option 5 for Date and DateTime, Option 1 for MonthDay and YearMonth.
Option 7 is fine with me, and is consistent with not having a .withCalendar() method on those types. Option 1 is also fine with me.
Option 7 sounds right.
Coincidentally, I think I chose the equivalent to Option 5 for the ZonedAbsolute proof-of-concept, because .with({timeZone}) has similar issues as with({calendar}). Here's the JSDoc for that behavior. Is this the same as Option 5?
* If the `timeZone` field is included, the result will convert all fields to
* the new time zone, except that fields included in the input will be set
* directly. Therefore, `.with({timeZone})` is an easy way to convert to a new
* time zone while updating the local time.
*
* To keep local time unchanged while changing only the time zone, call
* `getFields()`, revise the `timeZone`, remove the
* `timeZoneOffsetNanoseconds` field so it won't conflict with the new time
* zone, and then pass the resulting object to `with`. For example:
* ```
* const {timeZoneOffsetNanoseconds, ...fields} = ldt.getFields();
* const newTzSameLocalTime = ldt.with({...fields, timeZone: 'Europe/London'});
* ```
Is this the same as Option 5?
I believe so, yes.
My strong preference is for Option 7, because it is consistent with how with({timeZone}) is handled in LocalDateTime, and with({timeZone}) is actually required for a specific case: if you're changing the time zone and other LDT fields at the same time, what you want is to apply DST disambiguation at the end after you've played the new fields on top of the new time zone. If you split up into two method calls, then you can end up with disambiguation applied to the intermediate state which may make the final state one hour different than what you expected.
Also, with({timeZone, calendar}) is helpful and ergonomic. as opposed to requiring two method calls.
Conclusion from Sept. 11 meeting: we'll keep the existing behaviour, which is the status quo in the spec polyfill.
So, withCalendar can be removed from the polyfill?
My mistake, I meant "...status quo in the polyfill", so withCalendar should stay.
Most helpful comment
Option 7: what you said at the bottom. Option 5 for Date and DateTime, Option 1 for MonthDay and YearMonth.
Option 7 is fine with me, and is consistent with not having a
.withCalendar()method on those types. Option 1 is also fine with me.