Home | Send Feedback | Share on Bluesky |

A closer look at date-fns

Published: 8. January 2018  •  Updated: 22. August 2019  •  javascript

A year ago, I wrote a blog post about Moment.js, a very popular date/time JavaScript library.

The problem with Moment.js is that it imports a monolithic object with all of its methods. Even if you only need one or two methods, your application bundle contains everything. The library isn't that large, and when your application utilizes a lot of Moment.js functionality, it may not be an issue.

However, when you only need one method, it would be ideal to import only that specific method. This is where date-fns comes to the rescue. It's a collection of date/time functions that you can individually import into your application. In their documentation, they compare it to lodash, but for date objects.

See also this discussion about the differences between date-fns and Moment.js.

Size Comparison

In this section, we'll create a simple application using both libraries and compare the bundle sizes of the packaged applications. I'll use Parcel for the bundling process.

First, we create a new directory and a package.json file using npm init -y. Then, we install Parcel and the two libraries. For this test, I'm using Moment.js 2.24.0 and date-fns 2.0.0.

npm install parcel-bundler -D
npm install moment
npm install date-fns

Next, we create a directory src and add index.html, index-datefns.js, and index-moment.js. The examples will perform a few date calculations and print the results on the screen. You can find the complete source code of the project on GitHub: https://github.com/ralscha/blog/tree/master/datefns

To build the two versions, set the script tag in index.html to either <script src="./index-datefns.js"></script> or <script src="./index-moment.js"></script> and then call the bundler.

npx parcel build src/index.html --no-cache --public-url .

Bundle size:

The smaller size of the date-fns bundle is because the application only imported the required functions. With Moment.js, the application imported the entire library.

Examples

In this section, we'll take a closer look at some of the functions date-fns provides. This isn't a complete overview of all the methods that are part of the date-fns library. See the official documentation for more details and a complete list of all provided methods.

As mentioned before, date-fns is a collection of independent date and time functions that you have to import into your application individually. date-fns doesn't provide a date/time wrapper, nor does it add any methods to the built-in Date object. Instead, it works with the native Date object, seemingly seamlessly.


Parameters

Almost all methods in the library accept either a Date object or a number as a parameter. Internally, date-fns converts these parameters to a Date object using the toDate function. If you have a date string, you need to convert it manually using the parseISO function.

import toDate from 'date-fns/toDate';
const today = new Date();
const d1 = toDate(today);
const d2 = toDate(1515421707921);

import parseISO from 'date-fns/parseISO';
const d3 = parseISO('2018-01-11T03:30:03');
// d1 = Mon Jan 08 2018 15:30:09 GMT+0100 (W. Europe Standard Time)
// d2 = Mon Jan 08 2018 15:28:27 GMT+0100 (W. Europe Standard Time)
// d3 = Thu Jan 11 2018 03:30:03 GMT+0100 (W. Europe Standard Time)

The toDate function returns a clone when a Date object is passed to it. All functions in date-fns are immutable and don't modify their parameters. When a number is passed to the function, it's treated as the number of milliseconds elapsed since January 1, 1970, 00:00:00 UTC.

When you have functions that expect multiple parameters, you can either provide arguments of the same type or mix different types.

import isAfter from 'date-fns/isAfter';
import parseISO from 'date-fns/parseISO';
isAfter(new Date(2016, 11, 31), new Date(2017, 11, 31));
isAfter(1515422016315, 1515422016316);
isAfter(1515422016315, new Date(2017, 11, 31));
isAfter(new Date(2018, 0, 10), parseISO('2018-01-11T03:30:03'));
isAfter(1515422016315, parseISO('2018-01-11T03:30:03'));

Time Units

Instead of providing one function that supports multiple time units, date-fns provides multiple variants of a function. For example, the add function exists in these variants: addMilliseconds, addSeconds, addMinutes, addHours, addDays, addWeeks, addMonths, addQuarters, addYears, and addISOYears.

Not every function supports every time unit.


Parsing and Formatting

Converting a String to a Date object and vice versa is a common task in applications. For converting a String into a Date object, date-fns provides the parse function.

parse requires three parameters: the input string to be parsed, the description of the format as a string, and the baseDate. The function returns a Date object when it's able to parse the string successfully.

import parse from 'date-fns/parse';
const result = parse('31.01.2018', 'dd.MM.yyyy', new Date());
// Wed Jan 31 2018 00:00:00 GMT+0100

parse supports a wide variety of format tokens. See the official documentation for a complete overview.

The third parameter (baseDate) is used when you parse partial dates, like in this example.

const result = parse('January 2nd', 'MMMM do', new Date());
// Tue Jan 02 2018 00:00:00 GMT+0100 (W. Europe Standard Time)

The year is taken from the third parameter.


Conversely, from Date to string, we can use the format function. The format string (2nd parameter) supports the same tokens as parse.

import format from 'date-fns/format';
const result = format(new Date(2018, 0, 15), 'MM/dd/yyyy');
// 01/15/2018

const result = format(1515424147742, 'MMMM do, yyyy');
// January 8th, 2018

const result = format(parseISO('2018-06-15T11:30:00'), 'Do \'day of\' yyyy');
// 166th day of 2018

Set and Get

The set* and get* functions set a time unit to a specific value and retrieve a time unit's value, respectively. As mentioned before, date-fns doesn't change parameters, so the set* functions always return a new Date object with the changed value.

import getMinutes from 'date-fns/getMinutes';
let result = getMinutes(parseISO('2018-01-15T11:33:44.987'));
// 33
import getMilliseconds from 'date-fns/getMilliseconds';
result = getMilliseconds(parseISO('2018-01-15T11:33:44.987'));
// 987
import getYear from 'date-fns/getYear';
result = getYear(parseISO('2018-01-15T11:33:44.987'));
// 2018
import setSeconds from 'date-fns/setSeconds';
let result = setSeconds(1515424816229, 55);
// Mon Jan 08 2018 16:20:55 GMT+0100 (W. Europe Standard Time)
import setQuarter from 'date-fns/setQuarter';
result = setQuarter(new Date(2018, 0, 15), 3);
// Sun Jul 15 2018 00:00:00 GMT+0200 (W. Europe Summer Time)
import setMonth from 'date-fns/setMonth';
result = setMonth(parseISO('2018-01-15T11:33:44.987'), 6);
// Sun Jul 15 2018 11:33:44 GMT+0200 (W. Europe Summer Time)

Note that months are 0-based, and quarters start with 1.


Comparing

date-fns provides comparison functions like isAfter, isBefore, and isEqual, which check if the first date is after, before, or equal to the second date.

import isAfter from 'date-fns/isAfter';
let result = isAfter(new Date(2018, 0, 15), parseISO('2018-01-16'));
// false
result = isAfter(parseISO('2018-01-16'), new Date(2018, 0, 15));
// true
import isBefore from 'date-fns/isBefore';
let result = isBefore(1515424147742, 1515424147744);
// true
result = isBefore(parseISO('2018-01-16'), parseISO('2018-01-17'));
// true
import isEqual from 'date-fns/isEqual';
let result = isEqual(1515424147742, 1515424147742);
// true
result = isEqual(new Date(2018, 0, 15), parseISO('2018-01-15'));
// true
result = isEqual(new Date(2018, 0, 15), new Date(2018, 0, 16));
// false

For specific units, an isSame* function exists that checks if the specific unit is the same in the two provided dates.

import isSameWeek from 'date-fns/isSameWeek';
let result = isSameWeek(new Date(2018, 0, 15), parseISO('2018-01-16'));
//true

import isSameHour from 'date-fns/isSameHour';
result = isSameHour(1515424147742, 1515414137199);
//false

import isSameMonth from 'date-fns/isSameMonth';
result = isSameMonth(parseISO('2018-01-01'), parseISO('2018-01-31'));
//true

Arithmetic

The add* and sub* functions add and subtract a certain amount of a specific unit. Like the set* functions, these functions don't modify the provided parameter; instead, they return a new Date object.

import addWeeks from 'date-fns/addWeeks';
let result = addWeeks(new Date(2018, 0, 15), 2);
//Mon Jan 29 2018 00:00:00 GMT+0100 (W. Europe Standard Time)

import addMinutes from 'date-fns/addMinutes';
result = addMinutes(1515424147742, 60);
//Mon Jan 08 2018 17:09:07 GMT+0100 (W. Europe Standard Time)

import addYears from 'date-fns/addYears';
result = addYears(parseISO('2018-01-01'), 5);
//Sun Jan 01 2023 00:00:00 GMT+0100 (W. Europe Standard Time)

import subSeconds from 'date-fns/subSeconds';
let result = subSeconds(parseISO('2018-01-31T11:55:44.987'), 6);
//Wed Jan 31 2018 11:55:38 GMT+0100 (W. Europe Standard Time)

import subQuarters from 'date-fns/subQuarters';
result = subQuarters(parseISO('2018-01-31T11:55:44.987'), 1);
//Tue Oct 31 2017 11:55:44 GMT+0100 (W. Europe Standard Time)

import subMilliseconds from 'date-fns/subMilliseconds';
import getMilliseconds from 'date-fns/getMilliseconds';
result = subMilliseconds(parseISO('2018-01-31T11:55:44.987'), 187);
const ms = getMilliseconds(result);
//800

Weekdays

For weekdays, some special functions return the weekday and check if a date falls on a specific weekday. The getDay function returns a number between 0 (Sunday) and 6 (Saturday). For each weekday, an is* function exists, as well as isWeekend to check if a date falls on a weekend (Saturday or Sunday).

import getDay from 'date-fns/getDay';
let result = getDay(new Date(2018, 0, 8));
//1

import isMonday from 'date-fns/isMonday';
result = isMonday(new Date(2018, 0, 8));
//true

import isWeekend from 'date-fns/isWeekend';
result = isWeekend(new Date(2018, 0, 8));
//false

Intervals

date-fns supports a particular interval type that combines two dates. An interval object has two properties: start and end. Both properties can hold a Date or a number.

const interval1 = {start: parseISO('2018-01-01'), end: parseISO('2018-01-15')};
const interval2 = {start: new Date(2018, 0, 14), end: 1517353200000};

date-fns provides four functions that use intervals as parameters:

areIntervalsOverlapping: checks if two intervals overlap.

import areIntervalsOverlapping from 'date-fns/areIntervalsOverlapping';
const result = areIntervalsOverlapping(interval1, interval2);
// true

eachDayOfInterval: returns an array of dates with each day of the interval.

import eachDayOfInterval from 'date-fns/eachDayOfInterval';
const result = eachDayOfInterval(interval1);
/*
[
   Mon Jan 01 2018 00:00:00 GMT+0100 (W. Europe Standard Time),
   Tue Jan 02 2018 00:00:00 GMT+0100 (W. Europe Standard Time)],
   ....
   Mon Jan 15 2018 00:00:00 GMT+0100 (W. Europe Standard Time)
]
*/

getOverlappingDaysInIntervals: returns the number of days that two intervals overlap.

import getOverlappingDaysInIntervals from 'date-fns/getOverlappingDaysInIntervals';
const result = getOverlappingDaysInIntervals(interval1, interval2);
// 1

isWithinInterval: checks if a date is within an interval.

import isWithinInterval from 'date-fns/isWithinInterval';
const result = isWithinInterval(parseISO('2018-01-02'), interval1);
// true

Difference

The difference* functions return the difference between two dates in a specific unit.

import differenceInSeconds from 'date-fns/differenceInSeconds';
const result = differenceInSeconds(1515429928956, 1515429922956);
// 6

For days, weeks, months, quarters, and years, two functions calculate the difference. The differenceInCalendar* functions only consider the specific unit and return the difference. The other differenceIn* functions return the difference in full days, weeks, months, quarters, and years. For instance, differenceInDays calculates how many full days (24 hours) are between the two dates.

import differenceInMonths from 'date-fns/differenceInMonths';
let result = differenceInMonths(parseISO('2018-03-15'), parseISO('2018-01-17'));
//1

import differenceInCalendarMonths from 'date-fns/differenceInCalendarMonths';
result = differenceInCalendarMonths(parseISO('2018-03-15'), parseISO('2018-01-17'));
//2
import differenceInDays from 'date-fns/differenceInDays';
let result = differenceInDays(parseISO('2018-01-03T09:45:00'), parseISO('2018-01-01T11:30:00'));
// 1

import differenceInCalendarDays from 'date-fns/differenceInCalendarDays';
result = differenceInCalendarDays(parseISO('2018-01-03T09:45:00'), parseISO('2018-01-01T11:30:00'));
// 2

Another everyday use case is representing the difference between two dates in words, like "3 days" or "in 20 minutes". For this use case, date-fns provides three functions.

formatDistance

import formatDistance from 'date-fns/formatDistance';
let result = formatDistance(parseISO('2018-01-15'), parseISO('2018-01-01'));
//14 days

result = formatDistance(parseISO('2018-01-01'), parseISO('2019-04-01'));
//over 1 year

result = formatDistance(parseISO('2018-02-01'), parseISO('2018-01-01'));
//about 1 month

The function adds the word 'ago' and 'in' when addSuffix is set to true.

const result = formatDistance(new Date(2018, 7, 1), new Date(2018, 0, 1), {addSuffix: true});
//in 7 months

const result = formatDistance(new Date(2018, 0, 1), new Date(2018, 0, 15), {addSuffix: true});
//14 days ago

When includeSeconds is true, the string contains a more detailed message when the difference is less than a minute.

const result = formatDistance(1515424149742, 1515424147742);
//less than a minute

const result = formatDistance(1515424149742, 1515424147742, {includeSeconds: true});
//less than 5 seconds

formatDistanceStrict: Like formatDistance, but does not add words like 'almost', 'over', or 'less than'.

import formatDistanceStrict from 'date-fns/formatDistanceStrict';
let result = formatDistanceStrict(parseISO('2018-01-15'), parseISO('2018-01-01'));
//14 days

result = formatDistanceStrict(parseISO('2018-01-01'), parseISO('2019-04-01'));
//1 year

result = formatDistanceStrict(parseISO('2018-02-01'), parseISO('2018-01-01'));
//1 month

formatRelative: This function returns a string for the last and next 6 days, like 'last Sunday', 'yesterday', 'Tuesday'. For all other dates, it returns MM/dd/yyyy.

import formatRelative from 'date-fns/formatRelative';
let result = formatRelative(new Date(2018, 0, 4), new Date(2018, 0, 8));
//last Thursday at 12:00 a.m.

result = formatRelative(new Date(2018, 0, 7), new Date(2018, 0, 8));
//yesterday at 12:00 a.m.

result = formatRelative(new Date(2018, 0, 9), new Date(2018, 0, 8));
//tomorrow at 12:00 a.m.

result = formatRelative(new Date(2018, 0, 10), new Date(2018, 0, 8));
//Wednesday at 12:00 a.m.

result = formatRelative(new Date(2018, 0, 20), new Date(2018, 0, 8));
//01/20/2018

Internationalization

The library contains translations for several languages. See the documentation for a list of all currently supported languages.

To use a translation, you need to import the locale object.

import fr from 'date-fns/locale/fr';

and then pass it as an option to a function:

import formatDistance from 'date-fns/formatDistance';
const result = formatDistance(
                  new Date(2018, 0, 15),
                  new Date(2018, 0, 1),
                  {locale: fr, addSuffix: true}
               );
// dans 14 jours

Only format, parse, formatDistance, formatDistanceStrict, and formatRelative support internationalization.