JavaScript - - By Edwin Reynoso

Introduction to date-fns – a Lightweight JavaScript Date Library

Introduction to date-fns

Working with dates in JavaScript is a pain. Native date methods are often verbose and occasionally inconsistent — something which also makes them error-prone. But good news is at hand. There are several libraries that exist to take the pain out of date manipulation. These libraries are to JavaScript dates, what jQuery is to the native DOM API.

Let me give you an example. This is the accepted answer to a Stack Overflow question asking how to get last day of the month:

var t = new Date();
alert( new Date(t.getFullYear(), t.getMonth() + 1, 0, 23, 59, 59) );

Of course that works, but it’s not immediately obvious what the numbers after getMonth represent. Now contrast that with the considerably more readable:

const today = new Date();
console.log( lastDayOfMonth(today) );

That lastDayOfMonth method is one provided by date-fns, a self-proclaimed comprehensive toolset for manipulating JavaScript dates in the browser and Node.js.

In this article I’m going to show you how to get up and running with date-fns. After reading you’ll be able to drop it into your projects and take advantage of its many helper methods to manipulate dates with ease. This will make code like t.getMonth() + 1, 0, 23, 59, 59 a thing of the past.

So, Why Not Just Use Moment.js?

The elephant in the room is Moment.js. This library has undoubtedly become the de-facto standard for working with dates in JavaScript. So why not use that? Why do we need another JavaScript date library?

Well, according to Sasha Koss, the creator of date-fns, Moment.js has a few inherent problems which motivated him to create date-fns. Sasha expands on what these problems are on the project’s GitHub page, but in a nutshell:

  • Moment.js is mutable which can cause bugs.
  • It has a complex OOP API (which doubles the mutability problem).
  • It has a performance overhead due to the complex API.
  • Its build size is large when used with Webpack, as locale files are included as part of the bundle.

Let’s contrast that with date-fns (taken from the project’s homepage):

  • Date-fns is immutable, always returning a new date instead of changing the one you pass in.
  • It has a simple API. You always have one function that does one thing.
  • It is fast. You can be sure that your users will have the best user experience.
  • It is the perfect companion for Webpack. With the function-per-file style you can pick just what you need and stop bloating your project with useless functionality.

For me, the feature that makes it shine is its ease of use. When using it on a web-based project, you can just grab date-fns from a CDN, drop it into your page and profit. More about that next…

Installation

There are a variety of ways to install the library.

It’s available as an npm package:

npm install date-fns --save

Or via Yarn:

yarn add date-fns

Or Bower:

bower install date-fns

Or from a CDN:

<script src="https://cdnjs.cloudflare.com/ajax/libs/date-fns/1.28.5/date_fns.min.js"/>

If you chose this method, please note that the library is namespaced under a dateFns object, in the same way that jQuery is namespaced under $.

<script>
  console.log(
    dateFns.isToday(new Date())
  );
</script>

Date-fns Basic Usage

Assuming you’re using a module loader you can require only those parts you need. The following example shows you how to format a date.

const format = require('date-fns/format');
console.log(
  format(new Date(2017, 6, 6), 'MM/DD/YYYY')
);

// '06/07/2017'

Want to change the locale? No problem:

const format = require('date-fns/format');
const deLocale = require('date-fns/locale/de') // German

console.log(
  format(new Date(2017, 6, 6), 'DD MMMM YYYY', { locale: deLocale })
);

// 06 Juli 2017

Here’s a list of supported languages.

If you’re working in the browser, don’t forget to hang any calls to helper methods off of a dateFns object. Here’s how to determine the last day of the month using the aptly named lastDayOfMonth method:

<script src="https://cdnjs.cloudflare.com/ajax/libs/date-fns/1.28.5/date_fns.min.js"></script>
<script>
  console.log(
    dateFns.lastDayOfMonth( new Date(2017, 6, 7) )
  );
  // Mon Jul 31 2017 00:00:00 GMT+0200 (CEST)
</script>

Note that date-fns has equivalent methods for determining the last day of the week, quarter and year.

Immutability, Pureness and Simplicity

One of the selling points for date-fns is that its functions are pure, and simple, to explain. This leads to easy to understand code, which is easier to debug when things go wrong.

Let me demonstrate this using Moment.js as a counter example. As mentioned before, dates in Moment are mutable, which can lead to unexpected behavior.

const moment = require('moment');
const now = new Date(); 
const mNow = moment(now);

mNow.add('day', 3);
console.log(mNow.toDate());
mNow.add(3, 'day');
console.log(mNow.toDate());

// 2017-07-08T22:00:00.000Z
// 2017-07-11T22:00:00.000Z

There are a couple of things to take note of here. Moment’s add function is not fussy about the order in which it accepts its arguments (although the first method will now throw a deprecation warning). But more confusing is that if you call add multiple times in a row you, won’t get the same result because Moment objects are mutable:

mNow.add(3, 'day'); // add 3 days
mNow.add(3, 'day'); // adds 3 **more** days

Now compare that to date-fns which keeps arguments in one order and always returns the same result, returning a new Date object for each call.

let addDays = required('date-fns/add_days');
addDays(now, 3); // (April 30, 2017)
addDays(now, 3); // (April 30, 2017)
addDays(now, 3); // (April 30, 2017)

Also notice how the method name is more expressive (addDays instead of just add), keeping things consistent and having one method to do one thing and one thing only.

Watch Learn JavaScript Syntax and Programming Principles
Power your JavaScript code. Power the modern web.

Comparing Dates

If you look at the list of posts on SitePoint’s JavaScript channel, you can see that some are listed as being published on a certain date, whereas others are listed as being published X days ago. It might take a while if you tried to implement this in vanilla JavaScript, but with date-fns this is a breeze – just use the distanceInWords method.

Let’s compare two different dates.

const distanceInWords = require('date-fns/distanceInWords');
const startDate = new Date(2017, 3, 27); // (April 27, 2017)
const endDate = new Date(2018, 3, 27); // (April 27, 2018)
console.log(
  distanceInWords(startDate, endDate)
);
// "about 1 year"

In date-fns if you want to know the difference in another unit, you can substitute any of the following:

  • Milliseconds
  • Seconds
  • Minutes
  • Hours
  • Days
  • Months
  • Years

Where the method names are differenceIn[any of the above here] like differenceInDays.

Working With Collections of Dates

Date-fns has some very handy helper methods which you can use to manipulate collections of dates in all kinds of ways.

Ordering a Collection of Dates

The following example uses compareAsc to sort dates into ascending order. To do this, it returns 1 if the first date is after the second, -1 if the first date is before the second or 0 if dates are equal.

const compareAsc = require('date-fns/compare_asc');
const date1 = new Date('2005-01-01');
const date2 = new Date('2010-01-01');
const date3 = new Date('2015-01-01');
const arr = [date3, date1, date2];
const sortedDates = arr.sort(compareAsc);

// [ 2005-01-01, 2010-01-01, 2015-01-01 ]

As you can see the dates are now in ascending order.

The counterpart method to compareAsc is compareDesc.

const compareDesc = require('date-fns/compare_desc');
...
const sortedDates = arr.sort(compareDesc);
// [ 2015-01-01, 2010-01-01, 2005-01-01 ]

Generating the Days Between Two Dates

To generate the days between two dates, you can use the addDays method we met previously, as well as the eachDay helper which returns an array of dates within the specified range.

const addDays = require('date-fns/add_days');
const eachDay = require('date-fns/each_day');
const today = new Date();
const dayAWeekFromNow = addDays(today, 7);
const thisWeek = eachDay(today, dayAWeekFromNow); 
console.log(thisWeek);

// [ 2017-07-03, 2017-07-04, 2017-07-05, 2017-07-06, 2017-07-07, 2017-07-08, 2017-07-09, 2017-07-10 ]

Finding the Closest Date

Finding the closest date to a certain date in an array of dates can be done using the closestTo method. This code snippet follows on from the previous example:

const closestTo = require('date-fns/closest_to');
...
const christmas = new Date(2017, 11, 25);
const closestToChristmasDate = closestTo(christmas, thisWeek); 
// 2017-07-10T

There’s also the closestIndexTo method if you want to get the index of the array instead.

Time Zones

One disadvantage of date-fns is that it doesn’t currently have any time zone helper functions like Moment.js does. This Stack Overflow answer actually gives some background on how native Date objects don’t actually store “real time zone” data. However, date-fns always returns the local time zone that the code is running on.

In that thread you’ll notice that they mention a somewhat limited way of setting time zones natively in JavaScript. As the answer notes, this isn’t a comprehensive solution, but it works for many scenarios that require only output conversion (from UTC or local time to a specific time zone).

new Date().toLocaleString("en-US", {timeZone: "America/New_York"});

Time zones are actually a complicated problem to solve which is why MomentJS has a separate library for it. There are also plans afoot to add time zone support to date-fns, but at the time of writing, this is still a work in progress.

Conclusion

Date-fns is a great little library that puts a whole bunch of helper methods for working with dates in JavaScript at your finger tips. I hope this article has given you enough understanding and inspiration to go check it out and to start using it in your own projects. The library is under very active development and version 2 is (hopefully) just round the corner.

With that I leave you on your journey to get started with date-fns, please let me know how your experience goes with date-fns in the comments below.

Sponsors