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!


