DEV Community

Cover image for Ditch date-fns & momentjs: Use Temporal Date API Instead
Arafat
Arafat

Posted on

Ditch date-fns & momentjs: Use Temporal Date API Instead

We all know the pain of working with dates in Javascript. It needs to be more explicit, has almost no method, and could not be more clunky. For example, to create the Date January 1, 2023, you have to write a new Date (2023, 0, 1), which can be confusing for beginners, and overall just not that clear. And because of these reasons, the community has made many libraries that attempt to make Date easier to work with over the past years. Like momentjs or Date-fns. But the good news is that you won't need these third-party libraries any longer. The Temporal Date API in Javascript attempts to completely replace the Date object and fix all of the issues we generally face when working with dates.

🧭 Jump to


Introduction of Temporal Date API

Temporal API brings TONS of new data types and methods for handling various date-based concerns. The API has a unique global object called Temporal that allows you to have an easy-to-use API for date and time computations. The main goal of the temporal API is to provide more support for things like dates without times, times without dates, timezones, PlainDate, PlainTime, and PlainDateTime objects which don't have an association with a time zone. In this article, I will cover the essential parts of the API. However, I can’t go through everything about the API as It is massive. So if you want to go more in-depth on the API, you can read the full docs.

💡 NOTE: Throughout the article, I've used a lot of examples. But I needed help to come up with these examples. So instead, I've used an AI platform called Bito AI.

Bito AI

We all know the effectiveness of ChatGPT and its usefulness in programming. However, to use chatGPT, we need to visit their website. It can be frustrating when coding because it destroys momentum. What if you could add ChatGPT directly in your code editor? Bito AI does just that. It uses ChatGPT in the background to generate code, helps to debug, write test cases, generate code comments, create code explanations, and more. Bito AI even contains a lot of shortcut options and keyboard shortcuts. They also do a lot of on-the-fly engineering to give you the best answer without any work on your part. In general, it's ChatGPT, but specifically for coding purposes. I recently started using it and love it!

Temporal API Data Types

The Temporal API has different data types, divided into plain and zoned versions. Plain date/time helps represent a date/time without caring about the timezone. In contrast, a zoned date and time type are helpful when dealing with the exact time in a particular time zone.

Temporal.Instant

A Temporal.Instant represents a specific point in time and does not consider any particular calendar or location.

const today = Temporal.Now.instant()
console.log(today.toString())
// 2023-02-07T20:17:35.306655305Z

const date = Temporal.Instant.from("2023-02-07-06:00")
console.log(date.toString())
// 2022-01-01T06:00:00Z
Enter fullscreen mode Exit fullscreen mode

Temporal.ZonedDateTime

A Temporal.ZonedDateTime is a date/time object that contains all timezone-related information. It represents an actual event that has happened (or will happen) at a particular exact time from the perspective of a specific region on Earth.

const today = Temporal.Now.zonedDateTimeISO()
console.log(today.toString())
// 2023-02-07T14:17:35.306655305[America/Chicago]

const persian = Temporal.Now.ZonedDateTime("persian")
console.log(persian.toString())
// 2022-02-21T14:17:35.306655305[America/Chicago][u-ca=persian]

const date1 = Temporal.ZonedDateTime.from("2022-01-01")
console.log(date1.toString())
// 2022-01-01T00:00:00-06:00[America/Chicago]
const date2 = Temporal.ZonedDateTime.from({ year: 2022, month: 1, day: 1 })
console.log(date2.toString())
// 2022-01-01T00:00:00-06:00[America/Chicago]

const zonedDateTime = Temporal.ZonedDateTime.from({
  timeZone: 'America/Los_Angeles',
  year: 1995,
  month: 12,
  day: 7,
  hour: 3,
  minute: 24,
  second: 30,
  millisecond: 0,
  microsecond: 3,
  nanosecond: 500
}); // => 1995-12-07T03:24:30.0000035-08:00[America/Los_Angeles]
Enter fullscreen mode Exit fullscreen mode

Temporal.PlainDate

A Temporal.PlainDate object represents a date with no other information. It is not associated with a particular time or time zone.

const today = Temporal.Now.plainDateISO()
console.log(today.toString())
// 2023-02-07

const date = Temporal.PlainDate.from({ year: 2023, month: 2, day: 7 });
date.year; // => 2023
date.toString(); // => 2023-02-07
Enter fullscreen mode Exit fullscreen mode

Temporal.PlainTime

A Temporal.PlainTime object represents a time that is not associated with any timezone and date.

const today = Temporal.Now.plainTimeISO()
console.log(today.toString())
// 2023-02-07T14:17:35.306655305

const time1 = Temporal.PlainTime.from("04:03:25")
console.log(time1.toString())
// 04:03:25
const time2 = Temporal.PlainTime.from({ hour: 4, minute: 3, second: 25 })
console.log(time2.toString())
// 04:03:25
Enter fullscreen mode Exit fullscreen mode

Temporal.PlainDateTime

The Temporal.PlainDateTime object represents a date and time with no timezone information. You can create a new PlainDateTime by using the Temporal.Now.plainDateTimeISO method.

const today = Temporal.Now.plainDateTimeISO()
console.log(today.toString())
// 2023-02-07T14:17:35.306655305
Enter fullscreen mode Exit fullscreen mode

If you are working with a different calendar system than ISO 8601 then you can do the following

const today = Temporal.Now.plainDateTime("persian")
console.log(today.toString())
// 2023-02-07T14:17:35.306655305[u-ca=persian]
Enter fullscreen mode Exit fullscreen mode

If you want to create a new PlainDateTime with a custom date instead of just using the current time, there are two ways to do so.

(1) The first way is to give the method a date by specifying the year, month, and date. It can even have an hour, minute, second, millisecond, microsecond, nanosecond, and calendar.

const date = new Temporal.PlainDateTime(2023, 4, 1)
console.log(date.toString())
// 2023-04-01T00:00:00
Enter fullscreen mode Exit fullscreen mode

(2) Alternatively, you can use from method on the PlainDateTime object instead. This method takes either a string that matches a date or an object with keys for each part of the date.

const date1 = Temporal.PlainDateTime.from("2022-05-06")
console.log(date1.toString())
// 2022-05-06T00:00:00
const date2 = Temporal.PlainDateTime.from({ year: 2022, month: 4, day: 1 })
console.log(date2.toString())
// 2022-04-01T00:00:00
Enter fullscreen mode Exit fullscreen mode

Temporal.PlainMonthDay

Temporal.PlainMonthDate works like a PlainDate, However, It excludes any year information.

const date1 = Temporal.PlainMonthDay.from("01-01")
console.log(date1.toString())
// 01-01
const date2 = Temporal.PlainMonthDay.from({ month: 1, day: 1 })
console.log(date2.toString())
// 01-01
Enter fullscreen mode Exit fullscreen mode

Temporal.PlainYearMonth

Temporal.PlainYearMonth excludes any day information.

const date1 = Temporal.PlainYearMonth.from("2023-01")
console.log(date1.toString())
// 2022-01
const date2 = Temporal.PlainYearMonth.from({ year: 2023, month: 1 })
console.log(date2.toString())
// 2022-01
Enter fullscreen mode Exit fullscreen mode

Temporal.Duration

Temporal.Duration represents a duration of time. It is helpful for date/time arithmetic.

const duration = Temporal.Duration.from({
  hours: 130,
  minutes: 20
});

duration.total({ unit: 'second' }); // => 469200
Enter fullscreen mode Exit fullscreen mode

You can even use arithmetic like addsubtractwith, and round (I explained them later in the article) methods on durations. There are even a few additional helper methods that you should know.

const duration = Temporal.Duration.from({ hours: 200, minutes: 17 })
console.log(duration.negated().toString())
// -PT200H17M
console.log(duration.negated().abs().toString())
// PT200H17M
console.log(duration.total("minutes"))
// 12017
Enter fullscreen mode Exit fullscreen mode

Temporal.TimeZone

The Temporal.TimeZone represents a particular timezone. You use this with the from method or with the Temporal.Now.timeZone method.

const timeZone = Temporal.TimeZone.from('Africa/Cairo')
console.log(timeZone.toString())
// Africa/Cairo

const localTimeZone = Temporal.Now.timeZone()
console.log(localTimeZone.toString())
// America/Chicago
Enter fullscreen mode Exit fullscreen mode

Temporal.Calendar

Temporal.Calendar represents a calendar system. You can create a calendar using the from method.

const cal = Temporal.Calendar.from('iso8601');
const date = cal.dateFromFields({ year: 1999, month: 12, day: 31 }, {});
date.monthsInYear; // => 12
date.daysInYear; // => 365
Enter fullscreen mode Exit fullscreen mode

Temporal API Helper Methods

By now, we've seen a lot of data types. However, there are also a bunch of helper functions you can use to compare dates, use arithmetic with dates like add/subtract, convert types, and much more.

add and subtract

As It sounds, these two methods let you do addition and subtraction with two or more dates.

const today = Temporal.Now.plainDateISO() // 2022-02-07
console.log(today.add({ days: 4, months: 2 }).toString())
// 2022-04-11
Enter fullscreen mode Exit fullscreen mode

It also deals with overflow. For example, if the current date is February 7th, 2023, and you want to add one month to it, the resulting date should have been March 7th, 2023. However, the month of March doesn’t yet exist. Therefore, these results default to the nearest valid date, which would return February 28th. You can disable this behavior, though, with a second options argument.

const date = Temporal.PlainDate.from("2022-01-31")
console.log(date.add({ months: 1 }).toString())
// 2022-01-28
date.add({ months: 1 }, { overflow: "restrict" })
// Uncaught RangeError: value out of range: 1 <= 31 <= 28
Enter fullscreen mode Exit fullscreen mode

since and until

The since and until methods determine the distance between two temporal date objects.

const today = Temporal.Now.plainDateISO()
const yesterday = today.subtract({ days: 1 })
console.log(today.since(yesterday).toString())
// P1D
Enter fullscreen mode Exit fullscreen mode

The value returned by these methods is Temporal.Duration object. You can even pass options to these methods to fine-tune how you want the duration calculated.
If you specify the largestUnit, then the duration will be determined using that unit as the most significant value instead of the default value.

const today = Temporal.Now.plainDateISO()
const lastMonth = today.subtract({ months: 1, days: 4 })
console.log(today.since(lastMonth).toString())
// P35D
console.log(today.since(lastMonth, { largestUnit: "months" }).toString())
// P1M4D
Enter fullscreen mode Exit fullscreen mode

In the same way, the smallestUnit determines the duration by that unit as the smallest value instead of the default value. However, it could result in rounding, which you can further customize with the roundingIncrement and roundingMode options.

const today = Temporal.Now.plainDateISO()
const lastMonth = today.subtract({ months: 3, days: 4 })
console.log(today.since(lastMonth).toString())
// P96D
console.log(today.since(lastMonth, { smallestUnit: "months" }).toString())
// P3M
console.log(today.since(lastMonth, { smallestUnit: "months", roundingIncrement: 2 }).toString())
// P2M
console.log(today.since(lastMonth, { smallestUnit: "months", roundingMode: "ceil" }).toString())
// P4M
Enter fullscreen mode Exit fullscreen mode

equals

The equals method returns true only if the two temporal date objects have the same fields. Using == or === will return false unless the two objects are the same instance.

const today = Temporal.Now.plainDateISO()
const today2 = Temporal.Now.plainDateISO()
console.log(today === today2)
// false. Because they are two different instances
console.log(today.equals(today2))
// true
Enter fullscreen mode Exit fullscreen mode

with

The with method takes an object of fields to overwrite the current temporal date object. However, it doesn't overwrite the original date object. Instead, It returns a new temporal date object with the new value specified.

const today = Temporal.Now.plainDateISO()
console.log(today.with({ year: 2023, month: 3 }).toString())
// 2023-03-21
Enter fullscreen mode Exit fullscreen mode

round

This method rounds a temporal date to a specific unit.

const today = Temporal.Now.plainDateTimeISO()
console.log(today.round("hour").toString())
// 2022-02-22T14:00:00
Enter fullscreen mode Exit fullscreen mode

To modify the performance of the round method, you can pass an object that takes smallestUnit, roundingIncrement, and roundingMode.

const today = Temporal.Now.plainDateTimeISO()
console.log(today.round({ smallestUnit: "hour" }).toString())
// 2022-02-22T14:00:00
console.log(today.round({ smallestUnit: "hour", roundingMode: "ceil" }).toString())
// 2022-02-22T15:00:00
console.log(today.round({ smallestUnit: "hour", roundingIncrement: 6 }).toString())
// 2022-02-22T12:00:00
Enter fullscreen mode Exit fullscreen mode

Browser Support

I love Temporal Date API. However, this API has yet to be available as it is still in proposal stage 3. No browsers currently support this API, but you can use a polyfill if you want to start using this API today. Multiple polyfills are available for this API, but my favorite is @js-temporal/polyfill. Once you install this library, you can immediately start using the temporal API.


Conclusion

Everyone used to hate working with Dates in JavaScript, but with the introduction of the temporal API working with dates will be something you can enjoy doing.

Top comments (2)

Collapse
 
hatsantos profile image
Helder Santos

September 2024... and we are still waiting.

Collapse
 
quantuminformation profile image
Nikos

any news from the browser vendors about support?