Home | Send Feedback

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 the methods. Even if you only need one or two methods, the application bundle contains everything. The library is not that big, and when your application uses much Moment.js functionality, it may not matter.

But when you only need one method, it would be nice if there is a way only to import this specific method. Here comes date-fns to the rescue. A collection of date/time functions that you can individually import into your application. In the documentation, they compare it with lodash but for date objects.

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

Size comparison

In this section, we create a simple application with both libraries and compare the bundle size of the packaged application. I use Parcel for the bundling process.

First, we create a new directory, and a package.json file with npm init -y. Then we install Parcel and the two libraries. For this test, I use 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 make a few date calculations and print the result on the screen. You 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 a result of the fact that the application only imported the required functions, with Moment.js the application imported the whole library.

Examples

In this section, we are taking a closer look at some functions date-fns provides. This is not a complete overview of all 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 does not provide a date/time wrapper, and it does not add any methods to the built-in Date object, but it works with the native Date object seeming less together.


Parameters

Almost all methods in the library take either a Date object or a number as a parameter. Internally date-fns converts these parameters to a Date object with the toDate functions. If you have a date string, you need to convert that manually with 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 the function. All the functions in date-fns are immutable and don't change the parameters.
When a number is passed to the function, it is treated as a 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 the 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, addYear 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 string and the baseDate. The function returns a Date object when it is 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.


Reversely, 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. As mentioned before, date-fns does not change parameters, so the set* functions always return a new Date object with the changed value.

import getMinutes from 'date-fns/getMinutes';
const result = getMinutes(parseISO('2018-01-15T11:33:44.987'));
// 33
import getMilliseconds from 'date-fns/getMilliseconds';
const result = getMilliseconds(parseISO('2018-01-15T11:33:44.987'));
// 987
import getYear from 'date-fns/getYear';
const result = getYear(parseISO('2018-01-15T11:33:44.987')); 
// 2018
import setSeconds from 'date-fns/setSeconds';
const result = setSeconds(1515424816229, 55);
// Mon Jan 08 2018 16:20:55 GMT+0100 (W. Europe Standard Time)
import setQuarter from 'date-fns/setQuarter';
const 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';
const 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, quarters start with 1.


Comparing

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

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

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

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

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

import isSameMonth from 'date-fns/isSameMonth';
const 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 mutate the provided parameter; instead, they return a new Date object.

import addWeeks from 'date-fns/addWeeks';
const 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';
const result = addMinutes(1515424147742, 60);
//Mon Jan 08 2018 17:09:07 GMT+0100 (W. Europe Standard Time)

import addYears from 'date-fns/addYears';
const 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';
const 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';
const 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';
const 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 a is* function exists and isWeekend to check if a date falls on a weekend (Saturday or Sunday)

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

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

import isWeekend from 'date-fns/isWeekend';
const 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 take 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 look at 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';
const result = differenceInMonths(parseISO('2018-03-15'), parseISO('2018-01-17'));
//1

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

import differenceInCalendarDays from 'date-fns/differenceInCalendarDays';
const 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';
const result = formatDistance(parseISO('2018-01-15'), parseISO('2018-01-01'));
//14 days

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

const 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';
const result = formatDistanceStrict(parseISO('2018-01-15'), parseISO('2018-01-01'));
//14 days

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

const 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 the other dates it returns MM/dd/yyyy

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

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

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

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

const 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 language.

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.