Skip to content

Remove time zone and calendar protocols and classes #2826

@ptomato

Description

@ptomato

Conclusion in the champions meeting of 2024-04-18. We've been asked to reduce the surface area and complexity of the proposal. The callable hooks in the time zone and calendar protocols are the part that implementations have been most uncomfortable with. They also seem to be where we get the biggest reduction in complexity for the least loss of use cases. The TimeZone and Calendar classes themselves make less sense to keep if the protocols are gone. So our conclusion is to remove them.

Motivations for removing

  • Requiring observable calls in a particular sequence makes it difficult for implementations to optimize, unless they maintain separate code paths for built-in vs. user-supplied calendars/time zones.
  • Large increase in complexity of proposal for relatively little gain.
  • This functionality made more sense back when you could monkeypatch Calendar.from() and TimeZone.from() to ‘register’ a custom ID. That ability was removed when the proposal went to stage 3 because the committee will not agree to global state.
  • Without a calendar protocol, there doesn’t seem to be much point in having a Temporal.Calendar object. All the functionality is still easily available through PlainDate methods and properties, (except for Calendar.p.fields() and Calendar.p.mergeFields() which are weird methods that only exist to make it possible to build custom calendars with extra fields.)
  • Without a time zone protocol, the usefulness of a Temporal.TimeZone object is heavily reduced, although it doesn’t become completely redundant like Temporal.Calendar.

Use cases that disappear, and their replacements

Work around incomplete/outdated support in CLDR calendars and TZDB

For example:

Replacement: Previously, you could implement a custom calendar or time zone by creating an object that implemented the protocol, and monkeypatching some of Temporal so that it would deserialize dates with your custom calendar/time zone's ID into a Temporal object with the correct protocol object. You would now have to monkeypatch more of Temporal. As part of moving this issue forward, we'll create a proof of concept for doing this, to make sure that it remains possible.

Converting directly between Instant and PlainDateTime

Replacement: Go through ZonedDateTime, instead of through TimeZone. e.g.

// Before
timeZone.getPlainDateTimeFor(instant)
timeZone.getInstantFor(plainDateTime, { disambiguation })

// After
instant.toZonedDateTimeISO(timeZoneID).toPlainDateTime()
plainDateTime.toZonedDateTime(timeZoneID, { disambiguation }).toInstant()

Determine if two time zone IDs are aliases

Replacement: Use ZonedDateTime.p.equals() with the same instant. (This is more inconvenient, but it's a pretty uncommon operation. If this really needs to be more ergonomic, a dedicated static method can be added in a follow up.)

// Before
Temporal.TimeZone.from('Asia/Kolkata').equals(Temporal.TimeZone.from('Asia/Calcutta'))

// After
zonedDateTime.withTimeZone('Asia/Kolkata').equals(zonedDateTime.withTimeZone('Asia/Calcutta'))

Look up previous/next UTC offset transition in a time zone

Replacement: We'll add two methods to ZonedDateTime that replace the two methods on TimeZone.

// Before
timeZone.getNextTransition(fromInstant)
timeZone.getPreviousTransition(fromInstant)

// After
const fromZonedDateTime = fromInstant.toZonedDateTimeISO(timeZoneID);
fromZonedDateTime.nextTransition()
fromZonedDateTime.previousTransition()

Custom disambiguation behaviour

A niche use case is implementing PlainDateTime-to-Instant conversions with disambiguation behaviours other than the built-in earlier, later, compatible modes. By request, we have a cookbook recipe for this.
Replacement: You can still do it with two conversions, for example:

// Before
arrayOfInstants = timeZone.getPossibleInstantsFor(plainDateTime)

// After
arrayOfInstants = [
  plainDateTime.toZonedDateTime(timeZoneID, { disambiguation: 'earlier' }),
  plainDateTime.toZonedDateTime(timeZoneID, { disambiguation: 'later' }),
]

Scope of issue

  • Remove Temporal.Calendar
  • Remove Temporal.TimeZone
  • User-defined methods are not looked up nor called during calculations
  • Calendar-taking APIs no longer accept objects, only string IDs, and ISO 8601 strings out of which a string ID is determined
  • Time-zone-taking APIs no longer accept objects, only string IDs, and ISO 8601 strings out of which a string ID is determined
  • [[Calendar]] internal slot of PlainDate, PlainDateTime, PlainYearMonth, PlainMonthDay, ZonedDateTime only stores string ID
  • [[TimeZone]] internal slot of ZonedDateTime only stores string ID
  • Remove getCalendar() methods from PlainDate, PlainDateTime, PlainYearMonth, PlainMonthDay, ZonedDateTime prototypes
  • Remove ZonedDateTime.p.getTimeZone()
  • calendar property of object returned by PlainDate, PlainDateTime, PlainYearMonth, PlainMonthDay, ZonedDateTime's getISOFields() methods can only be a string
  • timeZone property of object returned by ZonedDateTime.p.getISOFields() can only be a string
  • Add ZonedDateTime.p.nextTransition() and ZonedDateTime.p.previousTransition()
  • Update TypeScript types to match the changes above.
  • Create proof of concept for how you would polyfill a custom calendar or time zone going forward

Metadata

Metadata

Assignees

No one assigned

    Labels

    normativeWould be a normative change to the proposal

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions