As developers, we often have to use and display different dates on our websites. Therefore, we often tend to use either the JavaScript internal Date specification API or an open-source abstraction library like Moment.js (please do not use this library anymore since it is deprecated by the contributors) or more modern libraries like Day.js and date-fns.
While those abstractions have a much better developer experience as you can handle dates in immutable ways and may provide you with more functions, for example, abstractions to calculate with dates, they need to be known and installed first, affecting the overall bundle size.
The temporal API
The Temporal API is a proposal that aims to solve the current problem of working with dates and likely will be an integrated browser alternative to the Date API in the JavaScript ecosystem.
There are several reasons to introduce a new API for handling dates instead of fixing the already existing Date API. Several issues with the current Date API are not fixable without introducing breaking changes but as the web evolves, we also want to ensure that old websites still work without having to change or upgrade code. Therefore, the TC39 is actively working on this new Temporal standard.
As mentioned by the official proposal and documentation site, the Temporal specification tries to solve the following problems:
- Providing easy-to-use APIs for date and time computations
- First-class support for all time zones, including DST-safe arithmetic
- Dealing only with objects representing fixed dates and times
- Parsing a strictly specified string format
- Supporting non-Gregorian calendars
In this post, I will investigate the Temporal API and will show how to use the API to solve some common StackOverflow questions users mentioned with the current Date API. Thereby, I want to explore the API surface and see what kind of benefits and downsides the new API provides.
First, I will introduce a problem and show how you can solve the problem using the Date API (which is introduced by // 👴 Date API
comment) in the second code part (which is introduced by // 🔥 Temporal
comment) I will show you a Temporal alternative.
You can either use the following CodeSandbox and code along or set up your own Parcel project and install the Temporal Polyfill. To convert Date objects to Temporal objects you have to introduce
1 2 |
import { toTemporalInstant } from "@js-temporal/polyfill"; Date.prototype.toTemporalInstant = toTemporalInstant; |
The final code is available here.
Problem 1: Date object is mutable (not fixable)
The non-immutability of the Date API is a frustrating and confusing topic for a lot of developers as can be seen in several open StackOverflow questions. The following example shows that whenever you want to create a new date object, you need to create a new instance new Date(). On the other hand, with the Temporal API you get a new Temporal object each time you calculate, e.g., add or remove days to a date. The non-immutability of the date object in JavaScript can explicitly bite you in longer functions where you forget to clone or make a new instance of a date instance.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// 👴 Date API const legacyNowDate = new Date(Date.now()); const legacyDateTomorrow = new Date(); legacyDateTomorrow.setDate(legacyNowDate.getDate() + 1); console.log(legacyDateTomorrow); // Thu Apr 07 2022 17:27:12 GMT+0200 (Mitteleuropäische Sommerzeit) // We can use the toTemporalInstant method to transform a // Date object into a Temporal instance. // 🔥 Temporal const nowTemporal = legacyNowDate .toTemporalInstant() .toZonedDateTimeISO(Temporal.Now.timeZone()); const temporalTomorrow = nowTemporal.add({ days: 1 }); console.log(temporalTomorrow.toString()); // 2022-04-07T17:27:12.615+02:00[Europe/Zurich] |
As you can see, we can even use the toTemporalInstant method to convert a legacy date into a Temporal date. There are two core advantages of using the Temporal API. First, you get a new object when using calculations and you also do not add an arbitrary value legacyNowDate.getDate() + 1 to the date. You explicitly define nowTemporal.add({ days: 1 } that you want to add one day to the Temporal object.
Problem 2: Convert dates between time zones
As mentioned by the following StackOverflow question, you can convert a date into another timezone by adding libraries like date-fns or writing a custom function converTimeZone like the one below. As mentioned in the StackOverflow thread, this solution can have some side effects.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// 👴 Date API const convertTimeZone = (date, timeZone) => { return new Date((typeof date === "string" ? new Date(date) : date).toLocaleString("en-US", {timeZone})); } const convertedDate = convertTimeZone("2012/04/20 10:10:30 +0000", "Asia/Jakarta") convertedDate.getHours(); // 1 console.log(convertedDate) // Tue Apr 20 2012 17:10:30 GMT+0700 (Western Indonesia Time) // 🔥 Temporal const source = Temporal.ZonedDateTime.from( "2012-04-20T10:10:30[America/Chicago]" ); const target = source.withTimeZone("Asia/Jakarta"); console.log(target.toLocaleString("en-US")); /// 4/20/2012, 3:10:30 PM GMT+7 |
With the Temporal API, you can use ZoneDateTime to work with timezone-aware dates. Multiple timezone-sensitive operations can be performed.
Problem 3: Adding and subtracting while accounting for daylight
As mentioned in the following StackOverflow issue, it is difficult to work with the standard date library when accounting for daylight. The Temporal API provides some useful helper methods like until() to calculate differences between two dates. We have already used and introduced the .add() method for when you want to add to dates. In the example below, we use until(), a method to calculate the example flight time in minutes between two Temporal ZonedDateTimes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// 👴 Date API const legacyDeparture = new Date("2017-05-08 12:55 GMT+01:00"); const legacyArrival = new Date("2017-05-08 17:10 GMT-08:00"); const diff = Math.abs(legacyDeparture - legacyArrival); const minutes = Math.floor(diff / 1000 / 60); console.log(minutes); // 795 // 🔥 Temporal const departure = Temporal.ZonedDateTime.from( "2017-05-08T12:55[Europe/Berlin]" ); // Berlin const arrival = Temporal.ZonedDateTime.from( "2017-05-08T17:10[America/Los_Angeles]" ); // Los Angelese const flightTime = departure.until(arrival); console.log(flightTime.total({ unit: "minutes" })); // 795 |
You could also specify different units, e.g., days or seconds. As you can see in the Date API example you had to make use of the Math library and convert the date object to a minutes unit yourself.
The neat thing about using Temporal API is that you could easily get in other formats like the total time in hours declaratively by console.log(flightTime.total({ unit: "hours" }));
without having to calculate with arbitrary values.
Problem 4: Getting an array of dates between two dates
As mentioned by the following StackOverflow issue, when you want to get dates between two dates, you can create a function and iterate over the dates and add one day to each iteration. This works but is also not really self-explanatory. The Temporal API offers an easy way of comparing Dates (which also can be used for sorting).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
// 👴 Date API const getBetweenTwoDatesDaysArray = (start, end) => { for ( var arr = [], distance = new Date(start); distance <= end; distance.setDate(distance.getDate() + 1) ) { arr.push(new Date(distance)); } return arr; }; console.log( getBetweenTwoDatesDaysArray(new Date("2022-05-01"), new Date("2022-06-03")).length ); // 34 // 🔥 Temporal const getBetweenTwoTemporalDates = (start, end) => { let arr = []; let distance = start; while (Temporal.PlainDate.compare(distance, end) < 1) { distance = distance.add({ days: 1 }); arr.push(distance); } return arr; }; console.log(getBetweenTwoTemporalDates(Temporal.PlainDate.from("2022-05-01"), Temporal.PlainDate.from("2022-06-03")).length) // 34; |
As you can see above, we use Temporal.PlainDate.compare(distance, end) to compare the dates. This function returns -1 when the starting date is smaller than the ending date (0 when they are the same and 1 if the end date is higher than the start date). Again, we use distance.add({ days: 1 }) to declaratively add one day to the distance date on each iteration.
Conclusion
Temporal API is already a strong, useful, and robust API for working with dates, times, and timestamps, and also makes it easy to do things that were hard or impossible to do with the Date API, like converting dates between time zones, adding and subtracting while accounting for daylight saving time, working with date-only or time-only data, and even handling dates in non-Gregorian calendars.
The API surface, on the other hand, is quite big and, as a developer, you have to get used to it first. Modern alternative libraries like date-fns and Dayjs provide even more useful helpers and are really small.
Furthermore, the Temporal API is not implemented in any modern browser so far, as mentioned by caniuse, but you can use a polyfill if you want to work with it already.
Looking at some of the examples above, most of the code could be simplified or written in a more declarative way, therefore I am excited about this new standardized way of handling the complexity we are having using dates. Also, the Temporal API solves a lot of confusion about mutable dates.
You can find some more awesome examples on the official Temporal API proposal cookbook and some additional content in the following blog post and link collection:
- Polyfill to use Temporal API in current production codebases https://github.com/js-temporal/temporal-polyfill
- The official proposal documentation https://tc39.es/proposal-temporal/docs/index.html
- Igalia blog post with additional clarification on why Temporal API is needed https://blogs.igalia.com/compilers/2020/06/23/dates-and-times-in-javascript/
- Historical background information https://maggiepint.com/2017/04/09/fixing-javascript-date-getting-started/l
Hey Jacob,
thank you very much for publishing this article. I liked reading it. Especially that you searched for common issues with the old Date API on Stack-Overflow is a great way of keeping authentic and user-oriented.
The Temporal API looks very promising. Too bad that currently none of the browsers support it. I hope it will be released soon as it makes life way easier and improves readability.