JavaScript’s Date
API has long been a source of frustration for developers due to its historical design flaws, including its:
To overcome these issues and limitations, developers have turned to libraries like Moment.js for more reliable and feature-rich date and time handling. Now, JavaScript has a new built-in solution on the horizon: the Temporal API, which brings a modern and intuitive approach to date and time manipulation.
In this article, we’ll examine JavaScript’s Date
API limitations, discussing the strengths and weaknesses of popular libraries like Moment.js, and delving into the Temporal API.
Date
APIBrendan Eich wrote JavaScript in 1995 within 10 days. During the rushed development process, the Date
API was created by copying the implementation of Java’s Date
object, inadvertently carrying over several of its issues. Nearly 30 years later, these same issues continue to frustrate developers.
One of the main flaws of the Date
API is its mutable nature. Any changes to a Date
object affect the original instance, potentially leading to unexpected behavior. Additionally, the API lacks intuitive methods for common date and time operations, forcing developers to write clumsy and error-prone code.
In the example below, we create two Date
objects, today
and tomorrow
. After setting tomorrow
to be one day after today
, both today
and tomorrow
end up pointing to the same date. This behavior can lead to unexpected results or bugs if not handled carefully:
const today = new Date(); const tomorrow = today; // Both variables reference the same object // Modify tomorrow's date tomorrow.setDate(tomorrow.getDate() + 1); // Since today and tomorrow reference the same object, both will be affected console.log("Today:", today); console.log("Tomorrow:", tomorrow);
Another issue shown in the above example is the lack of built-in methods for adding or subtracting dates. To add a day, we must extract the day with getDate()
, add to it, and then update the date using setDate()
. This approach makes the code harder to read and maintain:
// What we have to do using the Date API tomorrow.setDate(tomorrow.getDate() + 1); // What we expect tomorrow.addDays(1)
Other notable issues of Date
API include:
Date
API only supports the user’s local time zone and UTC, making cross-time zone calculations complexDate
API can’t represent a date without a time component. When creating a Date
object without specifying a time, it defaults to midnight (00:00:00
) in the local time zone. This can lead to unintended results in calculations or comparisonsDate
API’s lack of boundary checks can lead to unexpected results. When using the Date
constructor with values that exceed the valid range, the Date
object will automatically adjust the overflow into the next month without warning or error. For example, the new Date(2021, 1, 31)
unexpectedly represents March 3rd
Because the native JavaScript Date
API is so difficult to work with, various JavaScript libraries have emerged to simplify date and time manipulation, one of which being Moment.js.
Moment.js offers a more intuitive API for working with dates and times than the native Date
API. Below is an example:
// use Date API to add one day for tomorrow const date = new Date(); date.setDate(date.getDate() + 1); // use moment.js to add one day for tomorrow const momentDate = moment().add(1, "day");
As you can see, Moment.js provides a more concise and readable way to manipulate dates. It also provides many additional features, such as:
For more details, check out this guide to manipulating data and time using Moment.js.
Moment.js, while feature-rich, is mutable and heavy. The mutable nature can result in unexpected behavior if not used carefully. Furthermore, it was deprecated in 2020, meaning it is no longer actively maintained.
Fortunately, alternative libraries like date-fns offer similar functionalities. date-fns is immutable and has a tree-shaking feature, which allows us to load only the necessary components.
Now, JavaScript has a new built-in solution: the Temporal API. Temporal, currently a stage 3 proposal, aims to resolve many pain points in the JavaScript Date
API, including immutability, precision, and time zone management:
Source: [Temporal proposal - Introduction](https://tc39.es/proposal-temporal/#sec-temporal-intro)
The above diagram illustrates the Temporal date object string representation; it is structured to support plain dates, times, time offset, zoned date/time values, and all types of calendars.
Some of the Temporal API’s main features include:
Temporal API | Moment.js | date-fns | |
---|---|---|---|
Immutability | Yes | No | Yes |
Timezone support | Excellent | Limited | Good support after v4 |
Date time handling and formatting features | Extensive | Extensive | Extensive |
Performance | Better | Relatively large bundle size | Good |
Non-Gregorian calendars | Support | Support | No |
Precision | Nanoseconds | Millisecond | Millisecond |
Because the Temporal API is still in stage 3 and not yet available in most JavaScript environments, you’ll need to install a polyfill to use it at the time of writing.
To install the js-temporal/polyfill
, run the following command:
npm install @js-temporal/polyfill
Then, import it into the JavaScript project to start using Temporal features:
import { Temporal } from '@js-temporal/polyfill';
The Temporal API introduces several date types, including:
PlainDateTime
: Represents a date and time without a time zoneZonedDateTime
: Represents a date and time in a specific time zoneOther types, like Temporal.Duration
, handle time intervals precisely, making Temporal versatile for various date and time scenarios.
We can use a plain date object to represent a date without a time zone, which is ideal for scenarios like birthdays or deadlines.
We can create a plain date using Temporal.Now.plainDateISO()
or Temporal.PlainDate.from()
. Similarly, a plain date time object represents a date and time without a time zone, and plain time represents only a time:
// plain date const today = Temporal.Now.plainDateISO(); const newYear = Temporal.PlainDate.from("2024-01-01"); console.log(newYear.toString()); //2024-01-01 console.log(today.toString()); //2024-11-06 // plain date time const now = Temporal.Now.plainDateTimeISO(); console.log(now.toString()); //2024-11-06T20:24:57.927697925 // plain time const currentTime = Temporal.Now.plainTimeISO(); console.log(currentTime.toString()); //20:48:04.025084025
Another way to construct a plain date is from an object specifying the year, month, and day:
const firstDateNov = Temporal.PlainDate.from({ year: 2024, month: 11, day: 1 }); console.log(firstDateNov.toString()); // 2024-11-01
Zoned date time represents a date-time object with time zone details. We can use it for converting date/time across time zones or calculating date time that takes daylight saving time into consideration.
The Temporal API uses the IANA time zone database, which defines time zones with unique names in the form “Area/Location”, e.g., “America/New_York”:
// Zoned date: with time zone info const today = Temporal.Now.zonedDateTimeISO(); console.log(today.toString()); //2024-11-06T21:04:23.019063018+10:00[Australia/Sydney] const zonedDateTime = Temporal.ZonedDateTime.from("2024-10-30T10:00[Australia/Sydney]"); console.log(zonedDateTime.toString());//2024-10-30T10:00:00+11:00[Australia/Sydney]
A Temporal.Instant
represents a precise point in time. Unlike traditional JavaScript Date
objects, which are limited to milliseconds, Temporal.Instant
provides nanosecond precision. An instant is always in UTC, making it suitable for use cases like logging events from different time zones without worrying about local offsets:
const currentInstant = Temporal.Now.instant(); console.log('current instant:',currentInstant.toString()); //current instant: 2024-11-10T00:17:06.085826084Z const instantFromString = Temporal.Instant.from('2024-11-10T10:41:51Z'); console.log('from string:', instantFromString.toString()); //from string: 2024-11-10T10:41:51Z const sydneyTime = instantFromString.toString({ timeZone: 'Australia/Sydney' }); console.log('sydney time:',sydneyTime); //sydney time: 2024-11-10T21:41:51+11:00
In the Temporal API, duration represents time intervals, such as days, hours, or even years. It simplifies date and time calculations, like adding or subtracting time from specific dates or scheduling events over defined intervals.
Here’s an example of adding a duration to a date:
// Create a duration of 1 year, 1 months, and 10 days const duration = Temporal.Duration.from({ years: 1, months: 1, days: 10 }); console.log(duration.toString()); // "P1Y1M10D" // Add the duration to a date const startDate = Temporal.PlainDate.from("2024-10-30"); const newDate = startDate.add(duration); console.log(newDate.toString()); // 2025-12-10
Similarly, we can use duration to calculate times:
const timeDuration = Temporal.Duration.from({ hours: 3, minutes: 45 }); console.log(timeDuration.toString()); // "PT3H45M" // Subtracting time duration from a specific time const time = Temporal.PlainTime.from("12:00"); const newTime = time.subtract(timeDuration); console.log(newTime.toString()); // 08:15:00
The Temporal API allows us to perform operations like adding or subtracting time, rounding to specific units, and using with
to replace a date time component. The round
method rounds date and time values to the nearest specified unit. The with
method can replace specific components (such as year, month, or hour) without changing other parts:
const initialDateTime = Temporal.PlainDateTime.from("2024-11-01T10:23:45.678"); // Add 2 days and 5 hours const addedDateTime = initialDateTime.add({ days: 2, hours: 5 }); console.log("After Adding:", addedDateTime.toString()); // "2024-11-03T15:23:45.678" // Subtract 1 hour and 30 minutes const subtractedDateTime = addedDateTime.subtract({ hours: 1, minutes: 30 }); console.log("After Subtracting:", subtractedDateTime.toString()); // "2024-11-03T13:53:45.678" // Round to the nearest minute const roundedDateTime = subtractedDateTime.round({ smallestUnit: "minute" }); console.log("After Rounding:", roundedDateTime.toString()); // "2024-11-03T13:54:00" // Use 'with' to modify specific components (change only the month) const finalDateTime = roundedDateTime.with({ month: 12 }); console.log("Final Date-Time:", finalDateTime.toString()); // "2024-12-03T13:54:00"
A key advantage of the Temporal API is its focus on immutability. This means its value can’t be modified once a Temporal object is created. This immutability ensures that date and time calculations are predictable and avoid unintended side effects:
const now = Temporal.Now.plainDateISO(); const futureDate = now.add({ days: 5 }); // Modifying futureDate won't affect now futureDate.add({ hours: 2 }); console.log("Now:", now.toString()); // Now: 2024-11-07 console.log("Future Date:", futureDate.toString()); //Future Date: 2024-11-12
The Temporal API offers a significant performance boost over libraries like Moment.js and date-fns, primarily due to its native implementation and efficient handling of date and time operations.
Unlike Moment.js, which is bulky and mutable, the Temporal API doesn’t increase bundle size and its immutable design minimizes memory usage. Additionally, because it’s built into JavaScript (no extra parsing or overhead), Temporal is faster, especially for precise, high-volume date-time tasks like scheduling and time zone management.
With temporal.zonedDatetime
, we can easily handle time zones and scheduling. It allows us to work with local times and manage events across different time zones.
With Temporal.ZonedDateTime
, we can create a specific date and time within a chosen time zone, ensuring that operations like adding or subtracting time automatically respect time zone rules, including daylight saving adjustments:
// Create a ZonedDateTime in the "Australia/Sydney" time zone const sydneyTime = Temporal.ZonedDateTime.from( '2024-10-30T15:00:00[Australia/Sydney]' ); console.log(sydneyTime.toString()); // 2024-10-30T15:00:00+11:00[Australia/Sydney] // Convert to a different time zone const londonTime = sydneyTime.withTimeZone('Europe/London'); console.log(londonTime.toString()); // 2024-10-30T04:00:00+00:00[Europe/London]
When Daylight Saving Time (DST) begins, clocks are adjusted forward by one hour. This doesn’t change the time but shifts the time offset, making it appear like an hour has been skipped. Temporal handles these shifts automatically, ensuring calculations stay accurate across DST transitions:
// Adding 24 hours during a daylight saving change const beforeDST = Temporal.ZonedDateTime.from("2024-10-05T12:00:00[Australia/Sydney]"); const afterDST = beforeDST.add({ days: 1 }); // Automatically adjusts for DST console.log(beforeDST.toString());// 2024-10-05T12:00:00+10:00[Australia/Sydney] console.log(afterDST.toString()); // 2024-10-06T12:00:00+11:00[Australia/Sydney]
The Temporal API also makes scheduling and calculating event times across time zones easier.
In the example below, we convert an event scheduled in London time to Sydney’s local time using withTimeZone
. Then, we calculate the duration from Christmas to the event using since
and until
. since
measures the duration from a later date to an earlier one, while until
calculates from an earlier date to a later one:
const scheduledTime = Temporal.ZonedDateTime.from({ year: 2025, month: 1, day: 4, hour: 10, minute: 30, timeZone: 'Europe/London', }); console.log(scheduledTime.toString()); // 2025-01-04T10:30:00+00:00[Europe/London] const localEventTime = scheduledTime.withTimeZone('Australia/Sydney'); console.log(localEventTime.toString()); // 2025-01-04T21:30:00+11:00[Australia/Sydney] const christmasDate = Temporal.ZonedDateTime.from({ year: 2024, month: 12, day: 25 ,timeZone: 'Australia/Sydney'}); const timeUntilScheduled = christmasDate.until(scheduledTime, { largestUnit: 'hours' }); const timeSinceChristmas = scheduledTime.since(christmasDate, { largestUnit: 'hours' }); console.log(`Duration until scheduled: ${timeUntilScheduled.hours} hours, from Christmas ${timeSinceChristmas.hours} hours`); //Duration until scheduled: 261 hours, from Christmas 261 hours
We can also use the compare
helper method in the Temporal API to sort the event times for scheduling:
const sessionA = Temporal.PlainDateTime.from({ year: 2024, day: 20, month: 11, hour: 8, minute: 45, }); const lunch = Temporal.PlainDateTime.from({ year: 2024, day: 20, month: 11, hour: 11, minute: 30, }); const sessionB = Temporal.PlainDateTime.from({ year: 2024, day: 20, month: 11, hour: 10, minute: 0, }); const sessionC = Temporal.PlainDateTime.from({ year: 2024, day: 20, month: 11, hour: 13, minute: 0, }); // The events can be sorted chronologically or in reverse order const sorted = Array.from([sessionC, lunch, sessionA, sessionB]).sort( Temporal.PlainDateTime.compare ); console.log('sorted:', sorted); console.log('sorted reverse:', sorted.reverse()); // sorted: ["2024-11-20T08:45:00","2024-11-20T10:00:00","2024-11-20T11:30:00","2024-11-20T13:00:00"] // sorted reverse: ["2024-11-20T13:00:00","2024-11-20T11:30:00","2024-11-20T10:00:00","2024-11-20T08:45:00"]
While the Gregorian calendar is the most widely used, other calendars like the Hebrew, Islamic, Buddhist, Hindu, Chinese, and Japanese are sometimes needed to represent culturally or religiously significant dates. The Temporal API supports multiple calendar systems:
const eventDate = Temporal.PlainDate.from({ year: 2024, month: 12, day: 15 }); console.log(`Japanese calendar:${eventDate.withCalendar("japanese").toLocaleString('en-US', { calendar: 'japanese' })}`); //Japanese calendar:12/15/6 R console.log(`Hebrew calendar:${eventDate.withCalendar("hebrew").toLocaleString('en-US', { calendar: 'hebrew' })}`); //Hebrew calendar:14 Kislev 5785 console.log(`Islamic calendar:${eventDate.withCalendar("islamic").toLocaleString('en-US', { calendar: 'islamic' })}`); //Islamic calendar:6/14/1446 AH console.log(`Buddhist calendar:${eventDate.withCalendar("buddhist").toLocaleString('en-US', { calendar: 'buddhist' })}`); //Buddhist calendar:12/15/2567 BE
In the above example, we use withCalendar
to convert a date to various calendar systems. It helps display dates in the native formats of users from various regions.
As we noted earlier, the Temporal API is still a stage 3 proposal at the time of writing, meaning it may undergo changes and isn’t yet supported by all platforms. However, you can start experimenting with it now to prepare for future adoption.
When migrating from Moment.js or date-fns to the Temporal API, consider the following steps:
JavaScript’s Temporal API brings native support for immutability, time zone management, precise durations, and rich helper methods, without additional dependencies. Once Temporal becomes a core part of JavaScript, adopting it will keep your codebase modern, scalable, and ready for reliable date handling in the long term.
I hope you found this article useful. You can find the code snippets here.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowExplore use cases for using npm vs. npx such as long-term dependency management or temporary tasks and running packages on the fly.
Validating and auditing AI-generated code reduces code errors and ensures that code is compliant.
Build a real-time image background remover in Vue using Transformers.js and WebGPU for client-side processing with privacy and efficiency.
Optimize search parameter handling in React and Next.js with nuqs for SEO-friendly, shareable URLs and a better user experience.