DEV Community

Cover image for Master JavaScript date and time: From Moment.js to Temporal
Megan Lee for LogRocket

Posted on • Originally published at blog.logrocket.com

Master JavaScript date and time: From Moment.js to Temporal

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);
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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 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 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 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

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");
Enter fullscreen mode Exit fullscreen mode

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: Temporal API Proposal

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
Enter fullscreen mode Exit fullscreen mode

Then, import it into the JavaScript project to start using Temporal features:

import { Temporal } from '@js-temporal/polyfill';
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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] 
Enter fullscreen mode Exit fullscreen mode

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] 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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"] 
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 Signup

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!

Try it for free.

Top comments (0)