DEV Community

Cover image for Temporal Date API
Aaron Wolf
Aaron Wolf

Posted on • Updated on

Temporal Date API

Intro

This post is about my experience with the new Temporal API. We all know that dates in JavaScript suck. Temporal is the API that we will come to succeed the legacy date API in JavaScript.

At the moment (no pun intended), Temporal is still in stage 3 (out of 4) of development. This means that it should not be used in development, but if you're curious on how to use it you can still use the Temporal polyfill.

The reason why I'm writing this- despite there being a plethora of other tutorials- is because I haven't seen others showcase how Temporal might be used in the real world. However, this is not a comprehensive overview of the entire API.

The Paradigm Shift

In order to understand Temporal dates one must think about dates differently. In Legacy JavaScript dates are ultimately wrappers for epoch milliseconds. While Temporal dates can do this, they are not necessarily this. This means that you can have a Temporal Date object (or a time object) that is completely divorced from epoch milliseconds.

Most Common Use Cases (for me at least)

As far as I understand the naming convention, anything that begins with Plain is disconnected from epoch milliseconds.

PlainDate

One can create a plain date like so:

input:

// today can be an ISO string or an object
const today = '2022-09-10'
// or 
// const today = { year: 2022, month: 9, day: 10 }
const temporalToday = Temporal.PlainDate.from(today)
Enter fullscreen mode Exit fullscreen mode

temporalToday is an object with the following characteristics:

{
  calendar: Calendar {
    id: 'iso8601'},
  day: 10,
  dayOfWeek: 6,
  dayOfYear: 253,
  daysInMonth: 30,
  daysInWeek: 7,
  daysInYear: 365,
  era: undefined,
  eraYear: undefined,
  inLeapYear: false,
  month: 9,
  monthCode: "M09",
  monthsInYear: 12,
  weekOfYear: 36,
  year: 2022
}
Enter fullscreen mode Exit fullscreen mode

You can see that this provides a lot more information that simply the year, month, and date. Also, it's implied that the preferred calendar is iso8601, but there are other calendars that can be used, such as the Jewish or Chinese calendars.

PlainTime

Similar to PlainDate if the following is instantiated:

const time = '13:55:14'
const plainTimeNow = Temporal.PlainTime.from(time)
Enter fullscreen mode Exit fullscreen mode

The following object will be returned:

{
  calendar: Calendar {
    id: 'iso8601'},
  hour: 13,
  microsecond: 0,
  millisecond: 0,
  minute: 55,
  nanosecond: 0,
  second: 14
}
Enter fullscreen mode Exit fullscreen mode

PlainDateTime

Like PlainDate and PlainTime, this returns an object, but with the attributes of both of them together. Remember that the input must be in ISO format or an object.

const dateTime = '2022-09-10T13:55:14'
// or
// const dateTime = {
//   year: 2022,
//   month: 9,
//   day: 10,
//   hour: 13,
//   minute: 55,
//   second: 14
// }

const dateTimeNow = Temporal.PlainDateTime.from(dateTime)
Enter fullscreen mode Exit fullscreen mode
{
  ...
  day: 10
  dayOfWeek: 6
  dayOfYear: 253,
  daysInMonth: 30,
  daysInWeek: 7,
  daysInYear: 365,
  era: undefined,
  eraYear: undefined,
  hour: 13,
  inLeapYear: false,
  microsecond: 0,
  millisecond: 0,
  minute: 55,
  month: 9,
  monthCode: "M09",
  monthsInYear: 12,
  nanosecond: 0,
  second: 14,
  weekOfYear: 36,
  year: 2022
}
Enter fullscreen mode Exit fullscreen mode

This is pretty self explanatory once the previous 2 sections are understood.

ZonedDateTime

Notice that ZonedDateTime isn't preceded by the work "plain". This is because there is a full time and location, and therefore epoch milliseconds can be calculated.

Note that only an object or epoch nanoseconds can be passed into ZonedDateTime. I don't have a sample of using epoch nanoseconds since it's outside of my use case.

const hereAndNow = {
  year: 2020,
  month: 9,
  day: 10,
  hour: 13,
  minute: 55,
  second: 14,
  timeZone: 'America/New_York'
}
const zonedDateTime = Temporal.ZonedDateTime.from(hereAndNow)
Enter fullscreen mode Exit fullscreen mode

This will produce:

{
  day: 10,
  dayOfWeek: 4,
  dayOfYear: 254,
  daysInMonth: 30,
  daysInWeek: 7,
  daysInYear: 366,
  epochMicroseconds: 1599760514000000n,
  epochMilliseconds: 1599760514000,
  epochNanoseconds: 1599760514000000000n,
  epochSeconds: 1599760514,
  era: undefined,
  eraYear: undefined,
  hour: 13,
  hoursInDay: 24,
  inLeapYear: true,
  microsecond: 0,
  millisecond: 0,
  minute: 55,
  month: 9,
  monthCode: "M09",
  monthsInYear: 12,
  nanosecond: 0,
  offset: "-04:00",
  offsetNanoseconds: -14400000000000,
  second: 14,
  timeZone: {
    id: "America/New_York",
  },
  weekOfYear: 37,
  year: 2020,
}
Enter fullscreen mode Exit fullscreen mode

Look at all those attributes available to us! Notice that the epochMilliseconds are there along with epochMircoseconds and others.

This makes saving this information to the database extremely easy.

Instant

Instant can be instantiated from either a date string, or a number. The number can be epoch milliseconds, nanoseconds, or microseconds. Since I work with epoch milliseconds, that's what I'll show. Assume my timezone is UTC right now.

const todayString = '2022-09-10T13:55Z'
const todayMilliseconds = 1662818100000
Enter fullscreen mode Exit fullscreen mode

Note: There is an added Z at the end of the string date to denote the timezone.

There's also a slight difference to how one interacts with the two different types of dates.

Note: Objects cannot be passed in as an argument the way we did above with the Plain Temporal objects.

const fromString = Temporal.Instant.from(todayString)
const fromMilliseconds = 
  Temporal.Instant.fromEpochMilliseconds(todayMilliseconds)
Enter fullscreen mode Exit fullscreen mode

Both of these are the same date so they will output the same information.

{
  epochMicroseconds: 1662818100000000n
  epochMilliseconds: 1662818100000
  epochNanoseconds: 1662818100000000000n
  epochSeconds: 1662818100
}
Enter fullscreen mode Exit fullscreen mode

Coercion

The biggest frustration I had was figuring out how to coerce a Plain object into an Instant and vice versa. This section is the reason I'm writing this piece.

The problem I encountered was how do I read epochMilliseconds and coerce them into other Temporal types?

The answer! with and to.

Let's say I had a value in the db called createdAt (really clever, I know) which is stored as a UTC timestamp. How could this be coerced into a Temporal object (PlainTime, PlainDate, or PlainDateTime)?

The first step is to convert that into a ZonedDateTime. But before doing that a timezone is required. For this I'm going to use my local timezone.

const myTZ = Temporal.timezone.from('America/New_York')
const createdAt = 1662818100000
const createdAtInstant = 
  Temporal.Instant.fromEpochMilliseconds(createdAt)
const zonedCreatedAt = createdAtInstant.toZonedDateTime({
  timeZone: tz,
  calendar: 'iso8601'
})
Enter fullscreen mode Exit fullscreen mode

It's a bit much, but now zonedCreatedAt is a Temporal ZonedDateTime created from an instant that was created from an epoch millisecond timestamp.

If you wanted to convert the above to another Temporal type toPlainDate and toPlainTime can be used on zonedCreatedAt.

Conclusion

This was a bit of a primer. If you go through the documentation there are other ways to do some of the things I showed above and there are more details to know.

Latest comments (0)