Working with date and time in JavaScript
Most web applications use some sort of time and date wrangling — activity logs, notifications, actions of users. The timestamp is a commonly used field in our databases. But the way to display them on the front-end has always been a pain. Not to mention arithmetic operations on date and time. The current Date API is ill-equipped for our current use and we always end up using a third-party solution like day.js or my recent favorite date-fns.
A cool historical anecdote
In 1995, while working on Netscape, Brendan Eich was given only 10 days to implement date handling. To make things faster, he was told to implement Java's date handling at the time. Java updated its date handling two years later, but JavaScript developers still use the same, honestly terrible, API more than two decades later.
Source: Fixing JavaScript Date; Maggie's Blog
The Temporal API
Temporal API is a work-in-progress proposal for ECMAScript that promises to fix the issues we currently face with Date. According to the proposal, here are some things that the new API fixes:
- Providing easy-to-use APIs for date and time computations
- First-class support for all time zones, including DST-safe (Daylight Saving Time) arithmetic
- Immutable date objects
- Parsing date from a strictly specified string
- No support for non-Gregorian calendars
In this article, I aim to break down how Temporal API fixes the current issues with Date
.
Date and Time Arithmetic
Doing date and time arithmetic is troublesome and prone to errors using Date
. Temporal API comes with some handy methods that we can use to do simple arithmetic without any of the headaches. In the following examples, we do simple arithmetic on date and time by adding/subtracting from a given DateTime object. Temporal API returns a date object from the resulting arithmetic which we can use in our code.
// Create a Temporal DateTime object const date = Temporal.PlainDateTime.from('2021-09-20T08:14:55.899Z') // Add 5 days to given date date.add({ days: 5 }) // Temporal.PlainDateTime {_repr_: 'Temporal.PlainDateTime <2021-09-25T08:14:55.899>'}
In the above example, notice the <2021-09-25T08:14:55.899>
part. It's 5 days from the given date and time. We can do some cool stuff with the available methods.
// Add 5 days and 18 hours to given date date.add({ days: 5, hours: 18 }) // 2021-09-26T02:14:55.899 // Subtract 20 minutes from given date date.subtract({ minutes: 20 }) // 2021-09-20T07:54:55.899 // Define second date to calculate distance between two dates const secondDate = Temporal.PlainDateTime.from('2022-04-01T08:00:00.000Z') // Returns a Temporal.Duration object. const duration = date.until(secondDate) // Duration {_repr_: 'Temporal.Duration <P192DT23H45M4.101S>'} // The important part here is the P192DT23H45M4.101S. // Days until secondDate duration.days // 192 duration.hours // 23 duration.minutes // 45
Time Zones
Temporal API has first-class support for time zones as well. Normally Date
is restricted to UTC and produces date objects in the system's time zone. There is no direct way to use other timezones. For example, using Date
:
const date = new Date() const bangaloreDate = date.toLocaleString('en-us', { timeZone: 'Asia/Kolkata', }) const tokyoDate = date.toLocaleString('en-us', { timeZone: 'Asia/Tokyo' })
The toLocaleString()
returns a string so in order to do calculations we have to convert the result into a date object first.
In Temporal API, things are much simpler. The following code returns a Temporal DateTime object on which we can do further arithmetic.
Temporal.Now.zonedDateTimeISO('Asia/Kolkata') // 2021-09-20T14:13:30.295605379+05:30[Asia/Calcutta] Temporal.Now.zonedDateTimeISO('Asia/Tokyo') // 2021-09-20T17:47:45.999410295+09:00[Asia/Tokyo]
Immutable date objects
Date objects created with the Date() constructor are mutable. This means we can change the value of the object after initialization. This could lead to inconsistent state issues if not handled properly (not to say that mutable state is bad. It's memory efficient and simpler to use but causes issues in concurrency and parallel programming). Immutable data structures have become popular in recent years in the JavaScript ecosystem with Redux, Immutable.js, and Immer.
Temporal API instead returns immutable objects.
const date = Temporal.Now.plainDateISO() date // 2021-09-20 date.add({ days: 5 }) // 2021-09-25 date // 2021-09-20
Parsing date from strings
Parsing date from strings with Date
is prone to errors and unreliable since it returns different results because it returns UTC based time. For example:
new Date('2021-09-20').toString() // 'Mon Sep 20 2021 05:30:00 GMT+0530 (India Standard Time)' new Date('2021-09-20T00:00').toString() // 'Mon Sep 20 2021 00:00:00 GMT+0530 (India Standard Time)'
In the first line, I didn't specify a time but would expect midnight in my local time but it instead returned the offset based on UTC. Until I specified the time, it didn't return what I expected. Making this distinction is up to the programmer and is prone to errors. Temporal API makes a clear distinction by using separate methods.
// Returns UTC time Temporal.PlainDateTime.from('2021-09-20') // 2021-09-20T00:00:00 // Forces us to specify a timezone so it can return an appropriate value Temporal.ZonedDateTime.from('2021-09-20[Asia/Kolkata]') // 2021-09-20T00:00:00+05:30[Asia/Calcutta]
Calendars
The Date API currently supports only ISO-8601 which is an implementation of the Gregorian Calendar along with some additions. But that's only one calendar in use today. Many countries and regions use other calendars for religious and civil purposes.
const cal = new Temporal.Calendar('indian')
Once we have the cal
object, we can call methods on it to get day, month, year and more for a given date. For example:
cal.dayOfWeek('2021-09-20T07:54:24.270Z') // 1 cal.month('2021-09-20T07:54:24.270Z') // 6 cal.monthCode('2021-09-20T07:54:24.270Z') // M06 cal.year('2021-09-20T07:54:24.270Z') // 1943 cal.era('2021-09-20T07:54:24.270Z') // saka
Temporal API implements all the calendars supported by the Intl.DateTimeFormat object. It also supports the creation of custom calendars not in the above list. We can do this by creating a class that inherits from Temporal.Calendar
.
State of Temporal API
Like I previously mentioned, Temporal API is not production-ready yet. The Temporal API page on the TC39 website has an implementation that's coded to be spec-compliant but not optimized. We can play around with the API in the Devtools Console. The proposal is in Stage 3 which means that the specifications are finalized but further refinement will require feedback from implementations. Temporal API is currently in development for Chrome and Firefox but it's unclear when other browsers will implement them.
There's also no release date as of now. But I'm excited about this API because date and time operations make much more sense and are available natively without any external dependencies. You can read more about the proposal on the TC39 website and Github.