How to format dates without Moment.js
Tags: dates, performance, libraries, moment
Published: 2021-02-01 10:33:07

In this post I want to tell you how I got rid of the moment library when I found new alternatives with smaller, even non-existent, size.


{% tweet 1356181424827727874 %}


## The moment of Moment.js has passed 😥

Five years ago, even before I knew the frameworks I work with today, [Moment.js](https://momentjs.com/) was the library that saved us from headaches trying to handle [Date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) and its methods in its own way.

We are not going to deny, that when you start using a library and it solves your problem, you end up loving it and adding it to all the projects where you need to do some maths between dates or change your time zone. So you can imagine how many old projects use it.

> If you would like to know more about why it is necessary to upgrade to a new library, I invite you to read [Why you shouldn't use Moment.js...](https://inventi.studio/en/blog/why-you-shouldnt-use-moment-js)

But **now**, everything has changed and **we have a lot of alternatives** with which we can improve our performance. In my case, since in my website I only have a difference between dates and a few formatting, using only **Date** and [**Intl.DateTimeFormat**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat) I get what I need easily.

> Intl.DateTimeFormat is an object that enable language-sensitive date and time formatting.

But for those projects that need to deal with more complex functionalities, here are a few libraries for different use cases that may be useful for you.

## What alternatives do we have

Depending on how we use dates, there will be libraries that do better than others. Let's look at a few use cases and the top-rated libraries available.

### Older browsers

Let's start with **support for older versions of IE explorer**, although that will no longer be necessary from August 2021.

> Microsoft officially announced that it will [stop support for IE11](https://techcommunity.microsoft.com/t5/microsoft-365-blog/microsoft-365-apps-say-farewell-to-internet-explorer-11-and/ba-p/1591666) and we will have to do the same, for our own health 😂

As *Date.parse* only handles **ISO 8601** properly since ES5, to support IE9 we need libraries that use **their own regex solution**:

- [Luxon](https://moment.github.io/luxon/) - A **powerful**, **modern**, and **friendly wrapper** for Javascript dates and times (from the creators of moment).

    It also has a guide to help moment users to understand the new way of working: [https://moment.github.io/luxon/docs/manual/moment.html](https://moment.github.io/luxon/docs/manual/moment.html)

- [JS-Joda](https://www.npmjs.com/package/@js-joda/core) - An immutable date and time library for JavaScript. It provides a **simple**, **domain-driven** and **clean API** based on the ISO calendar system.

    It comes with built-in parsers for ISO 8601 and supports ECMAScript 5 browsers down to IE9.

![Meme Internet Explorer](https://dev-to-uploads.s3.amazonaws.com/i/zxsz8fa4anit7gixpkoe.jpg)

### Parsing

When it comes to **EPOCH Time Parsing**, most libraries do it properly, but that doesn't mean that there're some libraries that manage to do it faster:

> An **epoch** is a date and time from which a computer measures system time. The **Unix epoch** is the number of seconds that have elapsed since January 1, 1970.

- [Date fns](https://date-fns.org/) - Provides the most **comprehensive**, **simple** and **consistent** toolset for manipulating JavaScript dates in a browser & Node.js. Date-fns is fast so users will have the best user experience.

### Format

If we are concerned about **formatting** dates, **Intl.DateTimeFormat** or the **methods** provided by **Date** should be enough, but in case we need a library for other functionalities and we want to remain consistent throughout the project:

- [Day.js](https://day.js.org/) - Fast **2kB** alternative, is a **minimalist** JavaScript library that parses, validates, manipulates, and displays dates and times for modern browsers with a largely **Moment.js-compatible API**.

    If you use Moment.js, you already know how to use Day.js.

- [Format.js](https://formatjs.io/docs/guides/develop) is a **modular collection** of JavaScript libraries for internationalization that are **focused on formatting** numbers, dates, and strings for displaying to people.

    A set of core libraries built on the **JavaScript Intl** built-ins and industry-wide **i18n standards**, plus a set of integrations for common template and component libraries.

### Date time maths

But when our project requires to do **maths with dates**, we need a more powerful library to facilitate the operations and make it easier, without losing performance:

- **JS-Joda** is still the one that gives the **best performance** results. But taking into account that it is **not modular**, we must be sure about whether it is necessary. Although it must be said that its roadmap includes a plan for reducing its size.

    Support for the domain models *LocalDate*, *LocalDateTime*, *ZonedDateTime*, *Instant*, *Duration* and *Period*. Also, supports *IANA timezone*, adding the plugin [@js-joda/timezone](https://github.com/js-joda/js-joda/tree/master/packages/timezone) you'll have access to the IANA timezone database with all the timezones available.

### Package size

If, on the other hand, **size** is the most important factor, and Date and Intl are not enough, a modular library with **tree-shaking** or a **light-weight** library may be the best choice:

- **Day.js** - 2kB, less JavaScript to download, parse and execute, leaving more time for your code.
- **Date fns** - In this library, you can pick what you need and stop bloating your project with useless functionality. It works well with **modern module bundlers** such as webpack, Browserify, or Rollup and supports **tree-shaking**.

### Immutable

One thing that is important to know is that we left behind a library that mutated the objects we created, which could only lead to errors. From now on we will work with immutable objects!!

> An **immutable object** is an object whose state **cannot be modified after** it is **created**. This is in contrast to a mutable object, which can be modified after it is created.

## How I got rid of moment in my old projects

As I told you before, in my website I only had a couple of uses, so it was very easy to change. But in another project, I had a few problems with daylight savings and timezones.

In order not to forget, I'll explain the problems and the solutions I found without using any library:

### Formatting

- Format I wanted: **Jan 2021** (Short month and year)

  ```javascript
  // How it comes from my headlessCMS Storyblok
  const finishDate = '2021-01-31'

  // -- Before
  moment(finishDate).format('MMM YYYY')

  // -- After
  new Date(finishDate).toLocaleDateString('en-GB', {
    month: 'short',
    year: 'numeric',
  })
  // or (equivalent with Intl)
  new Intl.DateTimeFormat('en-GB', {
    month: 'short',
    year: 'numeric',
  }).format(new Date(finishDate))
  ```

- Format I wanted: **Sunday, 31 January 2021** (Name of the day, day number, long month and year)

  ```javascript
  // How it comes from my headlessCMS Storyblok
  const finishDate = '2021-01-31'

  // -- Before
  moment(finishDate).format('dddd, D MMMM YYYY');

  // -- After
  new Date(finishDate).toLocaleDateString('en-GB', {
    weekday: 'long',
    year: 'numeric',
    month: 'long',
    day: 'numeric',
  })
  // or (equivalent with Intl)
  new Intl.DateTimeFormat('en-GB', {
    weekday: 'long',
    year: 'numeric',
    month: 'long',
    day: 'numeric',
  }).format(new Date(finishDate))
  ```

When it comes to learn the options of Intl.DateTimeFormat, this [cheatsheet](https://devhints.io/wip/intl-datetime) has been a great help 😍

### Difference (Period of time)

On my website, I've represented the **period of time** I have been in each company. For that, I've calculated the difference between the entrance and leaving date:

![Period of time example](https://dev-to-uploads.s3.amazonaws.com/i/z4nwhu278e7j783u3xy1.jpg)

```javascript
// -- Before
const start = moment(this.start_date, 'YYYYMMDD')
const finish = moment(this.finish_date, 'YYYYMMDD')
const diff = moment.duration(finish.diff(start))

let formatDiff = ''
const year = diff.years()
const month = diff.months()

if (year > 0 && month > 0) {
  formatDiff += `${year} years & ${month} months` 
} else if (year > 0) {
  formatDiff += `${year} years`
} else if (month > 0) {
  formatDiff += `${month} months`
}
console.log(formatDiff)

// -- After
const start = new Date(this.start_date)
const finish = new Date(this.finish_date)
const diff = new Date(finish.getTime() - start.getTime())

let formatDiff = ''
const year = diff.getFullYear() - new Date(0).getFullYear() // 1970
const month = diff.getMonth()

if (year > 0 && month > 0) {
  formatDiff += `${year} years & ${month} months` 
} else if (year > 0) {
  formatDiff += `${year} years`
} else if (month > 0) {
  formatDiff += `${month} months`
}
console.log(formatDiff)
```

Once I got what I wanted, I set out to measure the times, to see if the change was also worthwhile in terms of performance. To my surprise, the change was much more than noticeable, going from ~0.10ms in moment to ~0.045ms using Date 😱

I measured it using [performance.now()](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now) as follow:

```javascript
const startTime = performance.now()

// -- Code calculating difference between dates

const endTime = performance.now()
console.log(endTime - startTime) // Result in milliseconds
```

> If you've calculated time periods in a different way, I would love to see your solution 😍, you can send me a DM at [@dawntraoz](https://twitter.com/dawntraoz).

### Working with Timezones

In backend, the common way to store a date in a database is in **UTC** format, so far so good.

But, when I created an instance of Date with the string coming from the DB, the result was set up to the browser's time zone.

The problem was related to the string coming from the DB, it didn't have any of the formats detected as UTC by Date. Adding 'Z' at the end of the string solved the issue.

```javascript
const dbDate = '2021-01-25 00:10:16.2451106';

// Problem: UTC 0, it's not the hour we set up
const incorrectUTC = new Date(dbDate);
console.log(incorrectUTC.toUTCString()); // "Sun, 24 Jan 2021 23:10:16 GMT"

// Solution: Z added
const correctUTC = new Date(`${dbDate}Z`);
console.log(correctUTC.toUTCString()); // "Mon, 25 Jan 2021 00:10:16 GMT"

// -- Date from my TimeZone (CET)
console.log(correctUTC.toString()); // "Mon Jan 25 2021 01:10:16 GMT+0100 (Central European Standard Time)"

// -- Date from a chosen TimeZone (America/New_York)
const americaDate = correctUTC.toLocaleString('en-GB', {
  weekday: 'short',
  month: 'short',
  year: 'numeric',
  day: 'numeric',
  hour: 'numeric',
  minute: 'numeric',
  second: 'numeric',
  timeZone: 'America/New_York',
  timeZoneName: 'short'
})
console.log(americaDate); // "Sun, 24 Jan 2021, 19:10:16 GMT-5"
```

While researching what was going on, I found in the documentation of [Date.parse in MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse#using_date.parse) the root of the problem:

The first string, according to ES5, will imply UTC time, and the others are specifying UTC timezone via the ISO date specification ('Z' and '+00:00')

```javascript
Date.parse("2019-01-01")
Date.parse("2019-01-01T00:00:00.000Z")
Date.parse("2019-01-01T00:00:00.000+00:00")
```

The following call, which does not specify a time zone will be set to 2019-01-01 at 00:00:00 in the **local timezone of the system**.

```javascript
Date.parse("2019-01-01T00:00:00")
```

### Timezones + Daylight saving rules

Another issue I came up while working with native Date.

Using an **offset to calculate Timezone is a wrong approach**, and you will always encounter problems while using it. Time zones and **daylight saving rules may change** on several occasions during a year, and it's difficult to keep up with changes.

To get the system's [IANA timezone](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) in JavaScript, you should use **Intl TimeZone** instead of Date offset:

```javascript
// Not what we need - Doesn't take into account daylight saving rules
const offset = (new Date()).getTimezoneOffset() * 60000;

// Solution
console.log(Intl.DateTimeFormat().resolvedOptions().timeZone)
```

But even though I have decided to use Date and Intl in my projects, I still have the itch to try out the library [Spacetime](https://github.com/spencermountain/spacetime).

**Spacetime** is a solution without Intl that calculate time in remote timezones, support daylight savings, leap years, and hemispheres. I have to take a look at it and put it into practice 👀 Share with me your thoughts if you already tried it!!

## Bye Bye moment  👋

And, well, we've come to the end, we've to leave moment behind. But, thanks to this library, many others have made their way to make our community a better place.

It only remains for me to say, thanks moment for everything you solved for us in its day and goodbye!!

![Vue moment library](https://dev-to-uploads.s3.amazonaws.com/i/ogguph0oi83cvouuvzp7.jpg)