Exploring Temporal

who here enjoys working with dates in JavaScript?

Hopefully it's gonna get a lot better so you can, you know, 'woo!' even more.

So temporal is an upcoming spec that provides a native modern date/time API including time zone support.

But I guess what's wrong with the existing implementation of date?

In 1995, Brendan Eich got the task of implementing JavaScript in Netscape all in less than a week.

And he was instructed just grab everything from Java for, date.

Java's cool.

He ported it across and two years later Java deprecated the entire date API . Meanwhile, 27 years later, here, we still are.

So there There's a bunch of things that I guess is wrong with date.

We don't have native time zone support.

We've got local time and we've got UTC it's mutable.

You can get yourself into all sorts of trouble by calling setDate and, all those, kinds of gotchas.

I also find the API just really difficult to use.

For example months, are zero indexed.

We're currently in month 10.

Yay.

So all those plus and minus ones throughout your code.

And we've got names like getTime that returns unique epoch time.

So there's a lot of room for improvement.

And, it's a bit hard to retrofit the existing date implementation.

There was a talk earlier that was talking about how jQuery is still on, I think 80% of websites.

So if that's still on 80% of websites, imagine how many websites currently use date and all the quirks and intricacies of the, current APIs.

Retrofitting date wasn't an option.

So they decided to create a brand new type, temporal with all the new features.

So yeah, enter temporal.

Here's an example of some temporal syntax.

It's very explicit and this is something you'll see as we go further through the API it forces you to be very explicit about what values you are storing.

So for example, here we've got a plainMonthDay, and that has no concept of a year.

We can combine it with a year and then now that is a plainDate.

That still has no concept of, time with, date.

I'm not sure about you, but I've run into heaps of issues where I forgot to pass in zero for hours, milliseconds, et cetera.

And then it took the current time for hours and milliseconds.

And then I was doing some duration calculations later and I'm like, what's going on?

And it turns out that I've got all this bad data in my dates.

A nice thing about temporal is if you leave the values blank they will default to zero.

So no, no kind of gotchas like that.

But they've got this concept of being explicit to start off with.

So if you don't care about the time maybe you're just doing a, calendar picker or something don't, store that at all.

And then we have nice convenience methods like this day of week to find out that this birthday is in on a Sunday.

So temporal is in stage three.

So final draft hopefully we'll be able to use it soon.

There are polyfillls according to their GitHub, they're not prod ready yet, but they seem to be nearing completion which is good.

And more importantly you can try temporal in dev tools right now if you want the TC 39 docs have loaded a a temporal polyfill onto the actual page.

They've named space temporal into the global name space as well.

So you can just use it as you'd expect to use it natively.

Before we get started into a bit more on temporal, just wanted to throw up a quick slide on ISO 8601 formatting.

I had no idea what this was a couple of months ago.

But I've seen it a bunch of times.

Yeah, created in 1988 last modified this year interestingly enough.

They re-added the support for expressing midnight as 24 or zero in the string.

Fun stuff.

But yeah, so you can see durations, for example they start with a p and the time is separated by a t with date times Zed indicates UTC time.

And then you can also indicate time zones with square brackets and calendar.

So it's just a, standard and temporal and the existing date use that.

A lot.

Probably the most basic item is to get the current time.

So temporal has the Temporal.Now type.

So you can get a plainDateISO or you can get a plainDateTime.

This again comes down to those explicit how much data do you actually need.

And then whenever you stringify, it'll go back into ISO format.

And you can see here we've got a bunch of kind of convenience properties that we can get.

They differ slightly for the different temporal types, but there are a lot of similar, ones across the types.

And my favorite is months are no longer zero indexed [crowd applauds].

Yay.

You can also get epochMilliseconds.

And now you can get epochSeconds, nanoseconds microseconds without having to do any calculations yourself.

So we have plainDates.

There a bunch of different ways to create new dates in temporal.

You can pass arguments directly or you can get it to parse from an ISO string.

So we have plainDate for storing just the year, month, day.

Then we have plainDateTime for storing time component as well.

We also have a plainTime in case you just, you're dealing with time.

And probably the more fun stuff, zonedDateTime.

So now we have a time zone associated with a type.

So those previous types had no concept of time zone.

Whereas here here we can add that in.

You can also express durations and there are couple of other types as well.

And here's a diagram of how they interact together.

ZonedDateTime becomes a lot more explicit whereas something like plainTime is very isolated.

And here's a diagram of how that relates to ISO formatting and the temporal types.

So what can we do with these types?

So we can combine them.

If we have a date and then we have wanna combine an hour with that date, we can do I use that birthday example before.

So this is again combining two different types.

And now we have dates that's aware of the year as well.

We can do operations, so we can add different units of time.

We can round to different units of time and we can subtract.

We can do comparisons, so we can do equality.

We can also get a duration for the distance between two dates.

/ And to put this into action, here's an example.

You have a flight from Hong Kong.

You can store that as a zonedDateTime.

Then you can express the duration of the flight as a temporal duration, and then you can add that duration and then convert into LA time.

So really expressive, very clear about what you're doing.

And then if we wanted to reverse engineer the flight time again from that example where we just had the two dates, you could just do until, and then you'll get a duration back, which is the same value that we, added.

Simple time zone conversions.

Very easy to get the latest time in LA you just create a new zoneDateTime, and then switch the time zone to LA.

So when you create a zonedDateTimeISO, it'll take whatever your default time zone is on the system for that.

So this is all very well and good, but at some point we generally need to display dates to users and we have dates stored that we want to parse.

So if you are going to and from ISO very easy this is how you do that as we've seen before.

But locale aware formatting is probably gonna be a bit more useful when we're displaying something in the UI.

So there's an internationalization API that's actually separate from temporal that's already in the browser, and that has all sorts of information about how to format different types of dates for different locales.

So you can see here if we want the US format where the month is in front, we can just pass in US.

And you know, we can provide a whole bunch of different options whether we want long, short values in the formatting.

And up the top you can see the toLocaleString.

So that's essentially a convenience method on a temporal date that will just put it through intl.dateTime format.

So I just grabbed this from the spec.

These are all the different configurations that you can pass into intlDateTime format.

So it's quite quite customizable in that regard.

And if that doesn't still work for you, you've also got this formatToParts method where you can get an object of all those different values in case you wanna do something different with them.

Now there's also a duration format for for durations, but this isn't actually in the browsers yet.

So this is also in stage three.

So if you play around with the temporal polyfill on that dev tools page and you try this you won't get that result.

You'll get the ISO string and a little message saying this hasn't been implemented yet.

Once, that gets implemented as well, then we'll be able to do stuff like this where we can localize durations as well.

And intl also has a relative time format.

So you often see those little things.

This post was published two minutes ago or something like that.

This also provides localization options for that.

But what if you want to go to and from a custom format?

I think with libraries like date-fns, we've got quite used to specifying dd in the template string what we expect.

We don't have this so much in temporal so your options are to write your own regex for, for parsing, for example.

Or you can still use an existing library like date-fns, because these existing date libraries they do have a lot of opinionated functionality.

And temporal has been designed not to just be a plug and play.

But what the, spec authors thought made the most sense.

They were looking at one stage of creating a bit of a shim for moment and some of the other date libraries that plugged into all the different temporal methods.

And so you could just port an existing moment code base to that but there hasn't really been any action on that, so we are not sure whether that's actually gonna make it.

You, might have to write a bit of custom code here.

So you can also convert temporal to legacy DateTime and back.

So this might be useful if you do decide that there's a, method in date-fns that you want to use.

But so you can convert that so.

And as an example here, data-fns has a very opinionated way of formatting relative dates.

For example if it's in this previous six days, then it'll be be last Sunday et cetera as per that table.

So if your app wanted that kind of behavior, then you know, you should probably usethat in date-fns then you can, and pass that through, or you can roll your own thing depending on the business logic.

I was reading about this and it was interesting.

They were saying that financial publications where time is very measured in the, milliseconds and whatnot.

Oftentimes news articles will be like, this was published two seconds ago, whereas for less time critical news, they might say that, ah if it's published in the last couple hours it's published now.

So a lot of that you know, how you interpret relative time and want to format that can be quite specific to your business logic.

So that's gonna be up to you to, to figure out what works best for you.

As a little aside, I I learned a bit about the internationalization API doing this, and I didn't know that there was a whole bunch of really interesting APIs out there already for internationalization for example number and currency format and different plural, pluralization in different languages.

So I thought that was quite interesting as well.

And you can also kind of convert time zones with intl.

But this is only really if you want to format it in that time zone so we can create a new date here and pass in what time zone we want it to be formatted in.

And it'll do that conversion.

Now I just kind touched the surface of temporal here, really, there's a lot more that they can do.

Overflow behavior.

You can pass in 13, for example, for a month.

And then you have to again be explicit.

Do you want it to overflow and become January or do you want it to throw an error because that's invalid input?

So there are a lot of different APIs in temporal, and you all have to be really explicit about the behavior that you expect.

I, read about an interesting example where in Sao Paolo, I think the time zone changed by an hour since 2018, I think it changed in 2019.

So if you have a date serialized in ISO that uses a, for example minus seven or, something code as opposed to the actual time zone name, and that's in a database somewhere.

But that was created in 2018 and now they've changed the time zone.

That's invalid.

So how do you wanna actually parse that, right?

It's do you want to pass it in the, new time zone and add an hour automatically?

Temporal has a whole bunch of different things that can handle those, edge cases and handle things like sorting as well.

So I was reading a Hacker News on temporal and I came across a, a funny story.

That there was a airline carrier and a front end dev went to work for them, and they, had this bug.

Some users couldn't book flights.

A little bit of a major, issue, right?

So you dug into the code and it turned out that you could only book flights if you're in a positive time zone So anyone who lived in a negative time zone was unable to book a flight.

So that's just an example of some of the the issues that you can have with time zones and, dates.

And so I'm hoping that with temporal these kinds of bugs will be a bit less rare or a bit -no, a bit rarer.

Sorry, I should say.

Because it'll be a lot easier for developers to do these kind of common operations.

So thanks for that.

And You can view all the links that I mentioned in this presentation at this link here.

Sorry about the long GitHub link I was trying to link to my personal website, but then I was having DNS issues, so backup solution link directly to the GitHub source So yeah, thank you.

And any questions?

[MC] Great, thank you.

Temporal is a new ECMAScript spec that provides a native modern date/time API, including timezone support.

What's wrong with Date?

What's wrong with Date?

https://maggiepint.com/2017/04/09/fixing-javascript-date-getting-started/
  • No support for time zones other than the user's local time and UTC
  • Parser behaviour so unreliable it is unusable
  • Date object is mutable
  • DST behaviour is unpredictable
  • Computation APIs are unwieldy
  • No support for non-Gregorian calendars

Enter Temporal

const birthday = Temporal.PlainMonthDay.from('12-15');
const birthdayIn2030 = birthday.toPlainDate({ year: 2030 });
birthdayIn2030.day0fWeek; // => 7

When can you use it?

Temporal is in Stage 3, a final draft before the spec gets approved.

ECMAScript Spec Stages

  • Stage 0 Idea
  • Stage 1 Proposal
  • Stage 2 Draft
  • Stage 3 Candidate
  • Stage 4 Ready

Can you use it now?

Yes, but polyfills are not prod-ready yet.

Try Temporal in DevTools now

https://tc39.es/proposal-temporal/docs/

ISO 8601 formatting

Created in 1988; last updated in 2022.

Date-times'2022-11-26T13:38:52.249Z'

  • Year-Month-Day
  • Hours:Minutes:Seconds.Milliseconds
  • Z = UTC time

Durations 'P3DT4H59M'

  • 3 days, 4 hours and 59 minutes

Basics

Temporal.Now

// Current date in ISO 8601 format
Temporal.Now.plainDateISO().toString() ;
'2022-11-27'

// Current date and time in ISO 8601 format
Temporal.Now.plainDateTimeISO().toString();
'2022-11-27T03:04:33.236673232

Basics

Temporal.Now

Screenshot of convenience properties in devtools

Months are not zero-indexed!!!

Basics

Temporal.PlainDate

new Temporal.PlainDate(2006, 8, 24)
Temporal. PlainDate.from('2006-08-24');
Temporal.PlainDate.from({ year: 2006, month: 8, day: 24 }) ;

Basics

Temporal.PlainDateTime

new Temporal.PlainDateTime(2006, 8, 24, 10, 23, 30)
Temporal.PlainDateTime.from ('2006-08-24T10:23:30');
Temporal. PlainDateTime.from({
	year: 2006,
	month: 8,
	day: 24,
	hour: 10,
	minute: 23,
	second: 30,
	millisecond: 0,
	microsecond: 0,
	nanosecond: 0
}); // = 2006-08-24T10:23:30.0000000

Basics

Temporal.PlainTime

new Temporal.PlainTime(13, 37) // => 13:37:00

Temporal.PlainTime.from('03:24:30');

Temporal.PlainTime.from({
	hour: 19,
	minute: 39,
	second: 9,
	millisecond: 68,
	microsecond: 346,
	nanosecond: 205
}); // => 19:39:09.068346205

Basics

Temporal.ZonedDateTime

new Temporal.ZonedDateTime(818299470000000000n, 'Africa/Cairo');

Temporal.ZonedDateTime.from('1995-12-07703:24:30+02:00 [Africa/Cairo]');

Temporal.ZonedDateTime.from({
	timeZone: 'Africa/Cairo,
	year: 1995,
	month: 12,
	day: 7,
	hour: 3,
	minute: 24,
	second: 30,
	millisecond: 0,
	microsecond: 0,
	nanosecond: 0
}); / = 1995-12-07T03:24:30.0000000+02:00 [Africa/Cairo]

Basics

Temporal.Duration

new Temporal. Duration (0, 0, 0, 40);

Temporal.Duration.from('P40D');

Temporal.Duration.from ({
	years: 0,
	months: 0,
	days: 40,
	hours: 0,
	minutes: 0
});

Types Summary

Shows 'Exact time types'–These types know time since Epoch, and includes 'Instant'. Also shows 'Calendar Date / Wall-Clock Time Types'–These have a calendar (except Plain Time). These include PlainMonthDay, PlainDate, PlainYearMonth, PlainDateTime. ZonedDateTime crosses both types. In between are the types TimeZone, Calendar, and Duration

Temporal <> ISO 8601

Shows an ISO 8601 / RFC 3339 date abd how it is broken up into Temporal types

So what can we do with these types?

Operations

Temporal.PlainDateTime.from('1995-12-07')
	.add ({ years: 20, months: 4, nanoseconds: 500 })
	.round ({ smallestUnit: 'hour' })
	.subtract ({ hours: 10 })
=> 2016-04-06T14:00:00

Comparisons

const dt1 = Temporal.PlainDateTime.from('2019-01-31715:30');
const dt2 = Temporal. PlainDateTime.from('2019-01-31T15:30');

dt1.equals(dt2); // => true

dt1.until('2022-01-01');
// => P1065D8H30M

Example: Flight Times

const calculatedFlightTime = departure.until(arrival);

'PT12H55M'

Example: Time in LA right now

Temporal.Now.zonedDateTimeISO()
	.withTimeZone( 'America/Los_Angeles')

To/from ISO 8601 string

Temporal.ZonedDateTime.from ('2022-02-2811:06:00.092121729+08:00 [Asia/Shanghai] [u-ca=chinese]').toString();
// => "2022-02-28T11:06:00.092121729+08:00 [Asia/Shanghai]
[u-ca=chinese]"

Temporal. PlainDate.from('2022-02-28').toString();
date.toString();
11 => #2022-02-28"

Temporal. Duration.from( 'P1DT12H30M'). toString();
"PIDT12H30M"

Locale-aware formatting

Intl.DateTimeFormat

Temporal.ZonedDateTime.from('2022-02-28711: 06:00.092121729+08:00 [Asia/Shanghai) (u-ca=chinese]*).toLocaleString('zh-CN', { calendar: 'chinese' });
'EA288 GMT+8 11:06:00'

new Intl.DateTimeFormat ('en-AU').format(date) ;
127/11/2022'

new Intl.DateTimeFormat(en-US').format(date) ;
'11/27/2022'

new Intl.DateTimeFormat ('en-AU', {
	day: 'numeric',
	weekday: 'long',
	month: 'long,
	year: '2-digit'
}).format(date);

'Sunday, 27 November 22'

Table reproduced from the Temporal Standard–Abstract Operations for DateTimeFormat Objects. Shows the "internal slots, property names and allowable values for the components of date and time formats".

Locale-aware formatting

new Intl.DateTimeFormat ('en-AU')
	.formatToParts (Temporal.Now.instant ())

screenshot of devtools showing object values in an array.

Locale-aware formatting

Intl.DurationFormat (Stage 3)

Temporal.Duration. from ( 'PT1H46M40S')
	.toLocaleString('fr-FR', { style: 'long' });
=> '1 heure, 46 minutes et 40 secondes"

Relative time formatting

new Intl.RelativeTimeformat('en', { numeric: 'auto' }). format(0, 'month')
'this month'

new Intl. RelativeTineFormat('en', { style: 'short' y). format(1, 'month') 'in 1 mo.'

new Intl.RelativeTimeFormat ('es') .format (-2.14, 'day')
'hace 2,14 días'

To/from from custom formats

Temporal does not support this.

Options:

  • Write your own code (e.g. RegExp for parsing)
  • Use an existing library like date-fns (e.g. convert to legacy Date)

Converting to/from regular dates

new Date(Temporal.ZonedDateTime.from('2020-01-01T00:00:01.001 [Asia/
Tokyo]').epochM1lliseconds)

new Date('2022-02-22T14:23:21.911Z').toTemporalInstant();

date-fns relative time formatting

Distance to the base date Result
Previous 6 day last Sunday at 04:30 AM
Last day yesterday at 04:30 AM
Same day today at 04:30 AM
Next day tomorrow at 04:30 AM
Next 6 days Sunday at 04:30 AM
Other 12/31/2017

Aside: Intl

ECMAScript's Internationalisation API

  • Intl.DateTimeFormat
  • Intl.Relative TimeFormat
  • Intl.DurationFormat (coming soon: in Stage 3)
  • Intl.Collator
  • Intl.ListFormat
  • Intl. NumberFormat
  • Intl.PluralRules
  • Intl.Segmenter

You can also convert timezones with Intl

Intl.DateTimeformat('en-AU', { timeZone:
"America/Los_Angeles', weekday: 'short'
hour: 'numeric', minute: 'numeric', second:
'numeric' }).format(new Date())

'Sat 7:43:08 am'

...if you just need to format the date.

Other things you can do in Temporal

A Story

More info

https://github.com/steveharrison/steveharrison.github.io/blob/master/temporal/index.md