Dealing with Dates and Times: Alternatives to Moment.js

Dealing with Dates and Times: Alternatives to Moment.js

One of the most crucial elements when setting up an application to be utilized in various languages is date formatting. Nobody wants to work with the Date object and end up breaking them; instead, they search for ready-made libraries that allow easy management of dates.

Moment.Js is one of the most popular JavaScript frameworks to format and modify dates, a date library for JavaScript that can parse, validate, manipulate, and format dates. However, some aspects of this library, such as its size or organizational style, can cause you to wonder if any better options are available.

We'll examine five Moment.js substitutes for date internationalization in this article:

  • JavaScript Internationalization API

  • Day.js

  • Luxon

  • Date-fns

  • Temporal

JavaScript Internationalization API

The ECMAScript Internationalization API's namespace is represented by the global object intl. This object has the following constructors for dates:

  • Date and time formatting is defined by Intl.DateTimeFormat().

  • Dates and timestamps can be expressed using the language-sensitive, readable words defined by Intl.RelativeTimeFormat().

  • Objects with the Intl.Locale() constructor represents a Unicode locale identification.

  • The Intl.NumberFormat() constructor for objects makes language-sensitive number formatting possible.

These constructors accept two optional inputs: the locale and an object containing options to change the output. For instance:

const timeFormat = new Intl.RelativeTimeFormat("de-DE");
const dateFormat = new Intl.DateTimeFormat("da");

According to BCP 47, the locale parameter is a string that provides a language tag, which consists of the following components:

  • Language code (ISO 639-1/639-2).

  • Script code (ISO 15924).

  • Country code (ISO 3166).

  • Variant (from iana.org), search for "Type: variant").

  • Extensions (from Unicode, more detail here).

Among the choices made available by Intl.DateTimeFormat is the date's format (full, long, medium, and short), whether to use a 12- or 24-hour time, and how to format the representation of various aspects of the day, such as the year, month, weekday, etc.

All the parameters you can use to customize this object are described in further detail on the Intl.DateTimeFormat documentation page.

The object only has the following fields for Intl.RelativeTimeFormat:

  • The recommended locale-matching algorithm is localeMatcher. Lookup (from the most specific to the least specific; if en-us is unavailable, en is picked) and best fit are two possible values (the default value, if en-us is not available, something like en-uk can be chosen).

  • to format the output message as numeric. The only two options are always (for instance, 2 hours ago) or auto, which occasionally disallows the output of numeric numbers (for example, yesterday).

  • style to format the output message's length. Long, short, and narrow are potential values.

The methods format() and formatToParts() can format a date after you have an object of type Intl.DateTimeFormat or Intl.RelativeTimeFormat. The latter function returns an array containing the output's parts.

When using Intl.DateTimeFormat, the formatting methods use the Date object as input:

const date = new Date(Date.UTC(2022, 7, 30, 3, 5, 0));
const options = {
  hour12: true,
  day: "numeric",
  month: "long",
  year: "2-digit",
  minute: "2-digit",
  second: "2-digit",
};
// Sample output: 30 Αυγούστου 22, 05:00
console.log(new Intl.DateTimeFormat("el", options).format(date));
// Sample output: 30. August 22, 05:00
console.log(new Intl.DateTimeFormat("de-AT", options).format(date));

If you only enter a few date-time elements in the options object, you will see the following in the output:

const date = new Date(Date.UTC(2022, 7, 30, 3, 5, 0));
const options = {
  day: "numeric",
  year: "2-digit",
};
// Sample output: 22 30
console.log(new Intl.DateTimeFormat("el", options).format(date));

The format() function for Intl.RelativeTimeFormat requires two arguments: the number to be included in the message and its unit (such as the year or second, in either the singular or plural form):

const options = {
  localeMatcher: "best fit",
  numeric: "auto",
  style: "short",
};
// Output: sem. ant.
console.log(new Intl.RelativeTimeFormat("es-ES", options).format(-1, "week"));
// Output: 上個月
console.log(new Intl.RelativeTimeFormat("zh-TW", options).format(-1, "month"));

A further distinction between utilizing the always and auto values for the numeric property is as follows:

// Output: in 1 day
console.log(
  new Intl.RelativeTimeFormat("en", { numeric: "always" }).format(1, "day")
);
// Output: tomorrow
console.log(
  new Intl.RelativeTimeFormat("en", { numeric: "auto" }).format(1, "day")
);

You can try to change all of the examples above; however, you could have some issues with your browser. Modern browsers provide good support for most Intl.DateTimeFormat, but only Chrome 71 and Firefox 70 completely support Intl.RelativeTimeFormat (Neither Safari nor Edge currently supports it).

Day.js

--

Day.Js is a lightweight library substitute for Moment.js. Day.js, by default, uses the American English locale. Other localities must be imported in the following way to be used:

 <script src= "path/to/dayjs/locale/de"></script>
    <script>
      dayjs.locale('de') // use locale globally
      dayjs().locale('de').format() // use locale in a specific instance
    </script>

The format() method in the example above returns a string that contains the formatted date. To format the date in a certain way, a string including the tokens might be used:

// Sample output: September 2022, Freitag
console.log(dayjs().locale(localeDe).format("MMMM YYYY, dddd"));

The additional functionality of Day.js is mostly provided by plugins, which you can load following your requirements. The UTC plugin, for instance, includes the following methods to obtain a date in UTC and local time:

let utc = require("dayjs/plugin/utc");
dayjs.extend(utc);

dayjs().format(); //2022-09-03T17:11:55+08:00

dayjs.utc().format(); // 2022-09-03T09:11:55Z

The advancedFormat, localizedFormat, relativeTime, and Calendar plugins can all be used for internationalization.

The format() method now has more formatting choices thanks to the advancedFormat and localizedFormat plugins (you can view all the options on the plugins documentation page):

let advancedFormat = require("dayjs/plugin/advancedFormat");
dayjs.extend(advancedFormat);

dayjs().format("Q Do k kk X x");

let localizedFormat = require("dayjs/plugin/localizedFormat");
dayjs.extend(localizedFormat);

dayjs().format("L LT");

let relativeTime = require("dayjs/plugin/relativeTime");
dayjs.extend(relativeTime);

dayjs().from(dayjs("1999-01-01")); // in 23 years

To display calendar time, the Calendar plugin introduces the calendar method (within a distance of seven days). The output doesn't appear to be localized:

let calendar = require("dayjs/plugin/calendar");
dayjs.extend(calendar);

dayjs().calendar(dayjs("2022-09-03"));
dayjs().calendar(null, {
  sameDay: "[Today at] h:mm A", // The same day ( Today at 2:30 AM )
  nextDay: "[Tomorrow at] h:mm A", // The next day ( Tomorrow at 2:30 AM )
  nextWeek: "dddd [at] h:mm A", // The next week ( Sunday at 2:30 AM )
  lastDay: "[Yesterday at] h:mm A", // The day before ( Yesterday at 2:30 AM )
  lastWeek: "[Last] dddd [at] h:mm A", // Last week ( Last Monday at 2:30 AM )
  sameElse: "DD/MM/YYYY", // Everything else ( 17/10/2025 )
});

Luxon

--

Luxon was developed by one of Moment's maintainers and incorporates many of its principles while making specific improvements. Luxon can be considered an internationalization wrapper for Intl.DateTimeFormat and Intl.RelativeTimeFormat.

To format dates when adhering to a locale, for instance, use the method toFormat(fmt:string, opts: Object) and date-time tokens from this table after first establishing the locale:

// Sample output: 2022 августа
console.log(luxon.DateTime.local().setLocale("ru").toFormat("yyyy MMMM"));

The options object, which the method accepts as an argument, can also contain the locale:

// Output: 2021 октября
console.log(
  luxon.DateTime.local(2021, 10).toFormat("yyyy MMMM", { locale: "ru" })
);

Alternatively, you can specify the locale at creation time if you're using methods like Object, ISO, Format, HTTP, or RFC2822:

const date = luxon.DateTime.fromISO("2021-10-19", { locale: "it" });
// Output: 2021 ottobre 19
console.log(date.toFormat("yyyy MMMM dd"));

However, using the methods toLocaleString() and toLocaleParts(), which produce an array containing the different components of the string and a localized string representing the date, respectively, is advised.

These methods use the same parameters (along with some presets, like DateTime.DATE_SHORT), making them similar to the Intl.DateTimeFormat methods format() and formatToParts() among others).

const date = luxon.DateTime.utc(2021, 10, 1, 9, 3, 1);
const options = {
  hour12: true,
  day: "numeric",
  month: "long",
  year: "2-digit",
  minute: "2-digit",
  second: "2-digit",
};
// Output: 1 octobre 21, 03:01
console.log(date.setLocale("fr").toLocaleString(options));
// Output: 1. Oktober 21, 03:01
console.log(date.setLocale("de-AT").toLocaleString(options));
/* Output: [{"type": "day", "value": "1"},{"type": "literal", "value":" "},{"type": "month", "value": "ottobre"},{"type": "literal", "value":" "},{"type": "year", "value": "21"},{"type": "literal", "value": ","},{"type": "minute", "value": "03"},{"type": "literal", "value": ":"},{"type": "second", "value": "01"}] */
console.log(
  JSON.stringify(date.setLocale("it").toLocaleParts(options), null, 3)
);
// Output: 9:03 AM
console.log(date.toLocaleString(luxon.DateTime.TIME_SIMPLE));
// Output: 10/01/2021
console.log(date.toLocaleString({ locale: "pt" }));

The only methods that provide functionality similar to Intl.RelativeTimeFormat are toRelative (which, by default, returns a string representation of a given time relative to now) and toRelativeCalendar (which, by default, returns a string representation of a given date relative to today).

// Sample output: 0 seconds ago
console.log(luxon.DateTime.local().plus({ days: 0 }).toRelative());
// Sample output: today
console.log(luxon.DateTime.local().plus({ days: 0 }).toRelativeCalendar());

The only issue with the methods mentioned above is that they won't be localized, unlike Intl.RelativeTimeFormat, If your browser doesn't support the stated API.

Session Replay for Developers

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — an open-source session replay suite for developers. It can be self-hosted in minutes, giving you complete control over your customer data

OpenReplay

Happy debugging! Try using OpenReplay today.

Date-fns

--

Date-fns is another well-liked JavaScript package for date processing and formatting. To utilize the most recent version, v2, directly in a browser, you'll need to use a bundler like Browserify because it is only available as an NPM package now.

This library has about sixty separate locations. To use one or more of them, you must import locales in this manner:

import { es, enCA, it, ptBR } from "date-fns/locale";

The following functions accept a locale as a parameter:

  • format, which accepts a date, a string specifying the formatting pattern (based on the date field symbols of Unicode Technical Standard #35), and an object containing parameters like the locale and the index of the first day of the week as arguments and returns the formatted date.

  • The formatDistance function returns the distance in words between the supplied dates after accepting as inputs the dates to compare and an object containing options such as the locale or whether to include seconds.

  • Similar to formatDistance, formatDistanceStrict does not employ any aids, such as almost, over, or less than. Features of the available options objects enable one to round fractional units and force a time unit.

  • formatRelative is a format that displays a date in words in relation to a base date. It can also accept an options object as input to set the position and index of the beginning day of the week.

Here are a few instances:

import {
  format,
  formatDistance,
  formatDistanceToNow,
  formatDistanceStrict,
  formatRelative,
  addDays,
} from "date-fns";

// Output: febrero / 23
console.log(format(new Date(), "MMMM '/' yy", { locale: es }));

// The Output: za manje od 10 sekundi
console.log(
  formatDistance(
    new Date(2022, 9, 1, 0, 0, 20),
    new Date(2022, 9, 1, 0, 0, 15),
    { locale: srLatn, includeSeconds: true, addSuffix: true }
  )
);


// Output: aproape 2 ani (assuming now is 15/02/2023 12:00)
 console.log(formatDistanceToNow(new Date(2021, 2, 15), { locale: ro }));


// Output: un minuto
console.log(
    formatDistanceStrict(
      new Date(2022, 9, 1, 0, 1, 20),
      new Date(2022, 9, 1, 0, 0, 15),
      { locale: it, unit: "minute"}
    ):

// Output: morgen om 18:38
console.log(
  formatRelative(addDays(new Date(), 1), new Date(), { locale: nlBE })
);

FormatRelative is frequently used in conjunction with helpers to add or remove various time units, such as addWeeks, subMonths, and addQuarters, among others.

Also, keep in mind that formatRelative will return the date provided as the first argument if the gap between the dates is greater than six days:

// If today is September 10, 2022 the output will be 17/09/2022
console.log(
  formatRelative(addDays(new Date(), 7), new Date(), { locale: ptBR })
);

Temporal

--

Temporal API for JavaScript aims to completely fix dates by adding a new global object named Temporal. Although relatively new, it's said to have provided different ECMAScript classes for scoped use cases like time-only, date-only, and others. Incorrectly assuming 0, UTC, or the local time zone for truly unknown values will result in problems and make code harder to read.

Temporal API

The majority of Temporal's APIs are divided into plain and zoned versions, which is the first significant difference that will stand out. A plain date/time represents a date or time without a timezone indication, which is the only distinction between these two types. On the other hand, a zoned date/time represents a certain date and time in a particular timezone. There are a couple of APIs we'd cover, which we'll discuss below.

PlainDateTime

Given that it just provides a date and time without taking the time zone into account, the PlainDateTime object is one of the simplest objects to comprehend. Using the Temporal.Now.plainDateTimeISO method is the simplest approach to creating a new PlainDateTime.

const today = Temporal.Now.plainDateTimeISO();
console.log(today.toString());
// 2022-09-03T14:17:35.306655305

TimeZone

A particular time zone is represented by the TimeZone data type. There are two most applied techniques for this, and Temporal.Now.timeZone is one of them, then you have the from technique. Now, You might use the constructor instead of the from 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

Calendar

The last data type you need to understand, and maybe the least, is the calendar data type. A calendar can be created using the constructor or, if you prefer, the from method.

const calendar = Temporal.Calendar.from("iso8601");
console.log(calendar.toString());
// iso8601

In terms of simplicity, the Temporal API fits well to cover dates and times. Although still in the testing phase, its founder urges developers to keep experimenting with its API to fix its bugs as the next generational library for solving internationalization problems with simplicity.

Although there are presently no browsers that support this API in any way, if you want to start utilizing it right away, you can use a polyfill@js-temporal/polyfill. You can use the temporal API as soon as you install this package.

Conclusion

Moment.js is a strong and established library for handling dates. However, aside from the fact that it's no longer in development, it could be excessive for some projects. In this article, I've analyzed four well-known libraries' approaches to date formatting in the context of internationalization in this article.

The JavaScript Internationalization API's features might be sufficient for straightforward use cases. Still, if you require a higher-level API (for instance, for relative times) and additional features like timezones or helper methods for adding or removing units of time, you might want to take a look at one of the other libraries examined in this article.

Resources

newsletter