DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 968,547 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Cover image for Temporal Date API
Aaron Wolf
Aaron Wolf

Posted 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. This, however, 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 lease)

As far as I understand the naming convention, anything that begins with Plain is disconnected to 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 (I conjecture) because now that there is a full time and location, epoch milliseconds can be calculated.

Note that only an object can be passed into ZonedDateTime (you can pass epoch nanoseconds in, but that's outside my use case, so I'm ignoring it).

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 things available to us! Notice that the epochMilliseconds are there along with epochMircoseconds and others.

This makes sending this information to the database 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 (and the db I work with) deals with epoch milliseconds, that what I'll show. Assume I'm at 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 trying to figure 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 called createdAt (really clever, I know) which is stored as a number. 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 (and a bit hastily written up). I will be following up with more little things that can be done with Temporal soon.

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.

Top comments (0)

This post blew up on DEV in 2020:

js visualized

πŸš€βš™οΈ JavaScript Visualized: the JavaScript Engine

As JavaScript devs, we usually don't have to deal with compilers ourselves. However, it's definitely good to know the basics of the JavaScript engine and see how it handles our human-friendly JS code, and turns it into something machines understand! πŸ₯³

Happy coding!