Article

Temporal: Getting a Second Date

Nick Krantz

February 20, 2024

A calendar in place of the cherry on top of a milkshake

If you’ve built a web application, more than likely, you’ve interacted with the JavaScript Date API. I believe every application I’ve worked on has used dates to some degree, both user-facing and under the hood with data management. Even with practice and repeated use, I still get caught up with some of the pain points of the Date API:

  • An instance of a Date object is mutable. Using a method like setDate will alter the existing date object in place and return the new timestamp. This has caused bugs for me in the past by still referencing the old variable, thinking it was still set to the original date.
  • Date’s are created with the user’s local timezone by default. new Date("2024–01–01").toString() will return "Sun Dec 31 2023 18:00:00 GMT-0600 (Central Standard Time)”. I didn’t give the Date a specific time which resulted in midnight being chosen for me. The date also represents the UTC time so depending on which methods I use toString vs toISOString I will get different outputs considering the users timezone.
  • The getMonth method returns a zero-based integer representing the date. Parsing a date in January with new Date("01–01–2024").getMonth() will return 0, which is not exactly intuitive when the date string uses months starting with one.

These pain points and others are commonly solved by diving into the NPM for Moment.js , Day.js , or Luxon . There will always be a place for third-party dependencies, but wouldn’t it be nice if the language itself were just better?

Temporal

Temporal is a top-level namespace that is currently a proposal in Stage 3 to be added to ECMAScript. Temporal will exist alongside the existing Date object and solves some of my personal pain points and others when it comes to JavaScript and dates.

Temporal provides separate ECMAScript classes for date-only, time-only, and other scoped use cases. This makes code more readable and prevents bugs caused by incorrectly assuming 0, UTC, or the local time zone for values that are actually unknown.

Temporal objects have different levels of Date/Time/Time zones abstractions, first class support date related arithmetic, and are immutable by default.

All three of these changes provide an improved experience, for example you have a single day but you want to get the day a week from now.

// PlainDate refers to an entire day, 
// no time or extra details associated with it
const jan15 = new Temporal.PlainDate(2024, 1, 15);

// Add 7 days to the original date
const nextWeek = jan15.add({ days: 7 })

console.log(nextWeek.toString())
// 2024-01-22

console.log(jan15.toString())
// 2024-01-15
// Temporal objects are immutable, the original object hasn't
// changed and the variable name is still accurate!

Wall-Clock Time vs Exact Time

A main distinction between different namespaces of Temporal is whether they encapsulate an exact time or a wall-clock time. An exact time (i.e. “UTC time” is the same time regardless of location. A wall-clock time or date represents what a user would see on their calendar or clock.

The only Temporal exact time type is Temporal.Instant. Temporal wall-clock time types are: Temporal.PlainYearMonth, Temporal.PlainMonthDay, Temporal.PlainDate, Temporal.PlainTime, and Temporal.PlainDateTime. Temporal.ZonedDateTime is both an exact point in time but also has time zone information stored with it so it is both an exact time and a wall-clock time.

I wrapped my head around all of the Temporal types by thinking of them in a hierarchical structure.

Hierarchy of Temporal Types. Starting at ZonedDateTime at the top showing exact time types on the left hand side and wall-clock time types on the right.

Temporal Arithmetic

Adding or subtracting dates usually involves getters & setters to alter your given day. Temporal comes with add & subtract methods use a Temporal.Duration instance and constrain the result to a valid date by default. These methods are immutable, returning new instances of the Temporal object.

const september2023 = new Temporal.PlainYearMonth(2023, 9)
const august2022 = september2023.subtract({ years: 1, months: 1 })
september2023.toString()
// '2023-09'
august2022.toString()
// '2022-08'
const january2023 = august2022.add({ months: 5 })
january2023.toString()
// '2023-01'

Determining the time between two Temporal instances can be done with since & until methods which return a Duration.

// December 25th 2023 8:30am
const dateTime1 = new Temporal.PlainDateTime(2023, 12, 25, 8, 30)
// April 2nd 2022 6:15am
const dateTime2 = new Temporal.PlainDateTime(2022, 4, 2, 6, 15)
let difference = dateTime1.since(dateTime2)
difference.toString()
// 'P632DT2H15M' = 632 days 2 hours 15 minutes

// `since` & `until` also take in units options to define largest or smallest units of time
difference = dateTime1.since(dateTime2, { largestUnit: 'years' })
difference.toString()
// 'P1Y8M23DT2H15M' = 1 year 8 months 23 days 2 hours 15 minutes

Availability

As of writing, Temporal is still currently in a proposal so it isn’t officially supported by any browser. If and when it is accepted into the ECMAScript spec you can check Can I Use for updated browser support.

For now, the Temporal Documentation website loads a polyfill into your browser, using the developer tools console you can try out the Temporal API there.

Even though Temporal is still in the proposal stage, I’m anxiously awaiting acceptance & full browser support. In the meantime I might have to start including the polyfill for my own projects!