Skip to content

Comparison and equality of dates with different calendars #625

@ptomato

Description

@ptomato

Follow up from #588.

When we add calendar slots to Temporal.DateTime, Temporal.Date, and Temporal.YearMonth, there's the question of whether two dates with different calendars but referring to the same day should be considered equal.

For example, these are all the same day:

  • Temporal.Date.from({ year: 2020, month: 5, day: 22, calendar: 'iso8601' })
  • Temporal.Date.from({ year: 2020, month: 5, day: 22, calendar: 'gregory' })
  • Temporal.Date.from({ year: 5780, month: 8, day: 28, calendar: 'hebrew' })
  • Temporal.Date.from({ year: 1441, month: 9, day: 29, calendar: 'islamic' })
  • Temporal.Date.from({ era: 'reiwa', year: 2, month: 5, day: 22, calendar: 'japanese' })

Temporal API users may want two different kinds of "equality":

  • "same day" — two objects refer to the same day but the calendar may be different
  • "object identity" ­— two objects have exactly the same internal state, i.e. don't ignore the calendar

It's unavoidable to prioritize one of these use cases above the other.

On #588 several ideas were proposed:

  1. Prioritize "same day". Lexicographical ordering of calendars by ID is a way to sort calendars, but it's not the only way, so it's better to make the user make that choice. If a user wants "object identity", then they specify their own way to sort calendars like this:
  • date1.equals(date2) && date1.calendar.id === date2.calendar.id
  • dates.sort((d1, d2) => Temporal.Date.compare(d1, d2) || d1.calendar.id <=> d2.calendar.id) (spaceship operator left as an exercise for the reader)
  1. Prioritize "object identity". Compare/sort the date ISO fields first and then the calendar ID last. Lexicographical ordering of calendars by ID is a sensible default and it's reasonable to write something from scratch if you want something different than that, and furthermore it's also consistent with how the corresponding ISO strings with [c=...] annotation would be sorted. If a user wants "same day", then they work around it like this:
  • date1.withCalendar('iso8601').equals(date2.withCalendar('iso8601'))
  • dates.sort((d1, d2) => Temporal.Date.compare(d1.withCalendar('iso8601'), d2.withCalendar('iso8601'))
  1. Try to mitigate the difference in convenience between the two use cases in some way, for example:
  • prioritize "object identity", but make compare() return ±2 if the dates differ, and ±1 if only the calendars differ but the dates are the same.
  • add an option to compare() and equals() such as { ignoreCalendar: true }, similar to Intl.Collator(..., { sensitivity: 'base' }).

Temporal.MonthDay is different, since it's not possible in the first place to compare two MonthDays from different calendars, which is why we removed Temporal.MonthDay.compare(). So I believe Temporal.MonthDay.equals() needs to do "object identity" in any case, which suggests that option 1 would make things inconsistent.

Metadata

Metadata

Assignees

No one assigned

    Labels

    calendarPart of the effort for Temporal Calendar API

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions