Written by Yan Sun✏️
JavaScript's Date
API has long been a source of frustration for developers due to its historical design flaws, including its:
- Unreliable parsing behavior
- Mutable nature
- Weak time zone support
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.
An introduction to the JavaScript Date
API
Brendan 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:
- Unreliable parser behavior. Date parsing can vary by the browser and time zone settings, and often dates are parsed in unpredictable ways, leading to incorrect results
- Lacks support for comprehensive time zone handling. The
Date
API only supports the user’s local time zone and UTC, making cross-time zone calculations complex - The
Date
API can’t represent a date without a time component. When creating aDate
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 comparisons - Handling Daylight Saving Time (DST) is another major challenge. The behavior of dates during DST transitions is often unpredictable, which can lead to errors in time-sensitive applications
- The
Date
API's lack of boundary checks can lead to unexpected results. When using theDate
constructor with values that exceed the valid range, theDate
object will automatically adjust the overflow into the next month without warning or error. For example, thenew Date(2021, 1, 31)
unexpectedly representsMarch 3rd
JavaScript’s Moment.js and date-fns libraries
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:
- Time zone handling
- Duration calculations
- Relative time formatting
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.
Introducing the Temporal API
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
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:
- Immutability: Temporal objects are immutable, meaning any date operations create new instances without modifying the original
- Precision: It supports nanosecond precision
- Complex date and time handling: Temporal has built-in support for time zones, daylight saving time, and calendar systems
- Improved performance: Temporal is built into JavaScript and designed to handle complex calculations. As a native solution, it doesn't increase bundle size and is more performant than external libraries
Comparison between the Temporal API, Moment.js, and date-fns
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 |
Basic usage of the Temporal API
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 zone -
ZonedDateTime
: Represents a date and time in a specific time zone
Other types, like Temporal.Duration
, handle time intervals precisely, making Temporal versatile for various date and time scenarios.
Plain date/time
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
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]
Instant
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
Duration
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
Calculating date and time: Add, subtract, round, and with
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"
Key advantages of the Temporal API: Immutability and performance
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.
Advanced usage: Time zones, scheduling, and non-Gregorian calendars
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.
Working with 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]
Scheduling events
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"]
Non-Gregorian calendars
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.
Migrating from Moment.js or date-fns to Temporal
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:
- Identify the key features you’re using in Moment.js or date-fns, and find their Temporal equivalents
- For larger projects, migrate incrementally. Start with core features like date creation and formatting, then tackle complex functions like time zones
- Consider building a date-time service to encapsulate all date and time functions to simplify maintenance and future updates
- Write thorough unit tests to ensure the migration preserves the original behavior
Conclusion
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.
LogRocket: Debug JavaScript errors more easily by understanding the context
Debugging code is always a tedious task. But the more you understand your errors, the easier it is to fix them.
LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to see exactly what the user did that led to an error.
LogRocket records console logs, page load times, stack traces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!
Top comments (0)