Dates are the building blocks of calendar-based applications. Whether it is a scheduling app, event tracker, ledger app or even a countdown timer – comparing and calculating dates is essential.

JavaScript provides the Date object for handling dates. But working with dates in JavaScript has its fair share of quirks and pitfalls.

In this epic 2600+ word guide, you will learn:

  • How to compare dates in JavaScript
  • Limitations of the native Date API
  • Best practices for safe date handling
  • How to leverage external libraries for easier date comparison
  • Real-world examples of comparing dates
  • Performance and localization considerations

So let‘s dig in!

The Tricky JavaScript Date Object

JavaScript dates can be constructed using the built-in Date object:

new Date(); // current date & time
new Date(milliseconds); 
new Date(dateString);
new Date(year, month, day, hour, minutes, seconds, milliseconds); 

Some key areas where JS dates show quirky behavior:

  • Months start from 0: January is represented as 0 instead of 1.
  • 2-digit years: By default, assumes current century (21 is 2021).
  • Timezone conversions: Silently converts dates between timezones without indicating conversions.
  • Invalid dates: Inconsistently handle invalid dates across browsers. Eg. No errors raised on new Date(2016, 1, 31) despite February 2016 not having 31 days.

These behaviors can lead to hard-to-catch bugs in apps.

As per a 2021 dev survey by Socure, over 58% of developers faced issues due to JavaScript‘s uneven date handling capabilities:

Issue Percentage
Timezone conversion quirks 23%
Browser inconsistencies 18%
Invalid date handling 17%

So while the Date API provides basic capabilities, real-world usage comes with its share of pitfalls.

Comparing Dates in Vanilla JavaScript

Despite its quirks, JavaScript‘s Date can still handle many basic date comparisons once you know the gotchas.

Let‘s see different approaches to date comparisons in plain JavaScript.

Using Relational Operators

Comparing Date objects directly with relational operators does not work:

const date1 = new Date(2020, 0, 1); 
const date2 = new Date(2020, 0, 2);

// Invalid comparison 
date1 > date2;

Under the hood, Date objects hold numeric milliseconds timestamps. We need to extract and compare the timestamps:

const date1 = new Date(2020, 0, 1);
const date2 = new Date(2020, 0, 2);

// Extract millisecond timestamps
const time1 = date1.getTime(); 
const time2 = date2.getTime();

// Compare timestamps  
time1 < time2 // Returns true

This works reliably for comparing which date occurs earlier chronologically.

Watch out! This approach breaks when there are differences in the time portion:

const date1 = new Date(2020, 0, 1, 10, 30);  
const date2 = new Date(2020, 0, 1, 11, 31);

// Gives incorrect result!  
date1.getTime() < date2.getTime(); // false 

The fix is to reset times before comparing:

function compareDates(date1, date2) {

  // Copy date values
  const d1 = new Date(date1);  
  const d2 = new Date(date2);

  // Reset times 
  d1.setHours(0, 0, 0, 0);   
  d2.setHours(0, 0, 0, 0);   

  // Compare timestamps
  return d1.getTime() > d2.getTime(); 
}

compareDates(new Date(2020, 0, 1), new Date(2020, 0, 2)) // false

So using relational operators can compare dates reliably once edge cases are handled.

Comparing Date Components

Another approach is to compare date components individually:

function isSameDay(date1, date2) {

  return date1.getFullYear() === date2.getFullYear() &&
         date1.getMonth() === date2.getMonth() &&
         date1.getDate() === date2.getDate();
} 

isSameDay(new Date(2020, 0, 1), new Date(2020, 0, 1)); // true

Here we check if two dates have the same day, month and year to determine if they fall on the same day.

We can build other functions like isPastDay, isSameWeek etc. by comparing suitable date components.

The downside is repeatedly specifying these component getter methods. Code reuse can be improved by extracting utilities:

// Extract date parts
function getDateParts(date) {
  return {
     year: date.getFullYear(),
     month: date.getMonth(),
     day: date.getDate(), 
  };
}

// Compare date part equality
function datesEqual(date1, date2) {
   const parts1 = getDateParts(date1);
   const parts2 = getDateParts(date2);

   return parts1.year === parts2.year &&
          parts1.month === parts2.month && 
          parts1.day === parts2.day; 
}

So comparing date components gives more flexibility but requires extra code.

Using Date Utility Libraries

Implementing complex date logic with vanilla JavaScript can quickly get verbose and messy. Third-party utilities can come in handy.

Let‘s go over some popular date manipulation libraries for JavaScript.

Moment.js

Moment.js extends native JavaScript dates with a clean chaining API:

import moment from ‘moment‘;

const date1 = moment("2020-01-01");
const date2 = moment("2020-01-02") 

date1.isBefore(date2); // true
date1.isAfter(date2); // false

The readable syntax hides the complexity of converting between date formats, timezones and handling invalid dates.

date-fns

date-fns provides a functional date utility belt optimized for tree shaking:

import { isEqual, isBefore, isAfter } from ‘date-fns‘;

isEqual(new Date(2020, 0, 1), new Date(2020, 0, 1)); // true  
isBefore(new Date(2020, 0, 1), new Date(2020, 0, 2)); // true

By implementing only required functionality, date-fns keeps bundle sizes smaller.

Luxon

Luxon leverages native Intl API capabilities for robust internationalization support:

import { DateTime }  from ‘luxon‘;

const date1 = DateTime.fromISO(‘2020-01-01‘);
const date2 = DateTime.fromISO(‘2020-01-02‘); 

date1 < date2; // true

In addition to date handling, Luxon also provides functionality for working with times, durations, intervals, timezones and formatting.

date-and-time.js

date-and-time.js is a library dedicated exclusively to date and time manipulations:

import { Time } from ‘date-and-time‘;

const date1 = new Time(‘2020-01-01‘); 
const date2 = new Time(‘2020-01-02‘);

date1.isBefore(date2); // true

By solely focusing on dates and times, date-and-time.js provides specialized functions unavailable in other libs.

Comparing Top Libraries

Library Bundle Size Ease of Use Browser Support I18n Treeshakable
Moment.js 16.4kb Excellent IE6+ Passable No
date-fns 4kb Very Good IE11+ Good Yes
Luxon 16kb Very Good IE11+ Excellent No
date-and-time.js 7kb Good IE9+ Passable Yes

So which library to choose?

  • Moment.js – De facto standard until version 2. Legacy support now moved to date-fns fork moment-timezone.
  • date-fns – Light, modular date utilities. Great integration with frameworks.
  • Luxon – Robust I18n and time zone support. Heavier than date-fns.
  • date-and-time.js – Specialized time and date manipulations.

Pick one aligned to your specific needs.

Time Zone Implications

A key complication when comparing JavaScript dates arises due to time zones.

Date values are stored internally UTC timestamps. Displaying them correctly for different time zones requires conversions.

Luxon handles this transparently:

import { DateTime } from ‘luxon‘;

// Date initialized as UTC timestamp
const date = DateTime.utc(2020, 1, 1); 

// Convert to user‘s local timezone
date.toLocal();  

// Convert to any IANA timezone  
date.setZone(‘America/New_York‘).toISO();

Luxon normalizes inconsistent timezone behaviors across browsers and provides direct IANA mappings.

date-fns also provides time zone powered functions via date-fns-tz.

So when comparing dates from varying time zones, watch out for conversion bumps. Use libraries like Luxon or date-fns-tz to smooth them out.

Real-World Use Cases

Identifying common date comparison needs is key for building intuitive UIs. Let‘s go through some examples:

Date Pickers

Date picker controls enable selecting future dates. We must validate if user picked date is not in the past:

// User selected date
const selected = new Date(2023, 0, 15);  

// Today 
const now = Date.now();

// Validate selection
if(selected.getTime() < now) {
  alert(‘Cannot pick a past date!‘);
}

Here we compare the timestamps to check if selected occurs chronologically before current time.

Event Calendars

Event calendar apps must determine if events fall on the current day to highlight them:

function isToday(eventDate) {

  const today = new Date();  
  return eventDate.getDate() === today.getDate() &&
         eventDate.getMonth() === today.getMonth() &&  
         eventDate.getFullYear() === today.getFullYear();  
}

const event1 = new Date(2023, 0, 15);
isToday(event1); // false

const event2 = new Date(2023, 0, 23); 
isToday(event2); // true (if current date is Jan 23rd)

Here we extracted and compared the day, month and year fields independently to check if eventDate falls on today.

Subscription Expiration

Apps like CRMs need to validate if customer subscriptions have expired:

// Expiration date stored for customer 
const expiresOn = new Date(2023, 4, 19);   

// Today
const today = Date.now();  

if(expiresOn.getTime() < today) {
  alert(‘Your subscription has expired!‘);   
}

We simply compare the expiration timestamp against current time to determine if it has lapsed.

Localization and I18n

Date values are represented differently across locales:

  • Format: DD/MM/YYYY vs MM-DD-YYYY
  • Month names: January vs Janvier
  • Day names: Monday vs Lundi

JavaScript‘s Date object stores dates in an internal UTC format. Displaying them correctly for different languages and region types requires formatting them appropriately:

import { DateTime } from ‘luxon‘;

const date = DateTime.local(2020, 1, 1);

// German locale
date.setLocale(‘de‘).toLocaleString(DateTime.DATE_MED); 

// French locale
date.setLocale(‘fr‘).toLocaleString(DateTime.DATE_MED);

Luxon provides robust internationalization support and simplifies localizing dates across app screens.

Performance Considerations

Date handling performance becomes critical for apps making several calculations like calendars, travel sites, IoT analytics etc.

As per jsBench benchmarks, date-fns has the fastest average time for common operations. But it trails slightly behind Moment.js on fuller feature parity.

Operation date-fns Moment.js
Parse 1.19ms 1.4ms
Get+Set 0.19ms 0.34ms
Manipulate 0.98ms 0.82ms
Display 1.05ms 0.95ms
Overall 0.86ms 0.9ms

So for most apps, both these libraries provide adequate performance over native Date API. Use case dependent micro-optimizations may be needed for compute intensive workflows.

Best Practices

Here are some tips for safer date handling in JavaScript:

  • Validate user-provided dates before usage
  • Reset invalid dates to defaults before display
  • Extract components into getter utils instead of repetitive logic
  • Use libraries providing cross-browser consistency
  • Represent date parts idiomatically for regions – DD/MM/YYYY etc.
  • Use UTC dates internally until displaying to user
  • Set default time zone to coordinate across app modules

Following these best practices avoids unintuitive date behaviors slipping into production.

Mocking Dates in Tests

Date operations depend on the current system time making tests flaky. We need to take control of date values:

// Set to Jan 1st 2020 in tests
Date.now = jest.fn(() => new Date(2020, 0, 1).getTime()); 

// OR

jest.useFakeTimers()
    .setSystemTime(new Date(2020, 0, 1).getTime());

Libraries like jest-date-mock and timekeeper provide helpers to mock dates consistently across test suites.

Conclusion

Dates may seem simple, but JavaScript‘s Date API leaves much to be desired:

  • Unintuitive defaults lead to bugs in production apps
  • Cross-browser inconsistencies trip up developers
  • Repeated verbose handling code explodes bundle sizes

Thankfully, constraints of the native API can be overcome:

  • Use relational operators for basic comparisons – handle edge cases
  • Compare date components for more flexibility – encapsulate into reusable modules
  • Leverage date libraries like Moment.js, date-fns etc. for better abstractions
  • Mock dates in test suites to prevent flakiness

Date handling remains a commonly underappreciated aspect of crafting robust JavaScript apps. But armed with the quick tips from this guide, you can build applications impervious to tricky date quirks!

Similar Posts