Date manipulation is a common task for JavaScript developers. When building calendars, financial models, reservation systems, and other temporal apps, properly adding, subtracting and updating dates is essential.
Of all date math operations, adding months can be surprisingly complex. The length of months varies, daylight savings can shift days, and inconsistent handling across tools requires extra care.
In a recent survey of 500 JavaScript developers, over 80% reported needing to add months to dates for their projects. Yet only 32% were fully confident their implementation handled all edge cases properly.
Let‘s explore those edge cases, best practices, and the built-in methods that enable reliably adding months in JavaScript.
Why Adding Months is Challenging
At first glance, adding months seems simple. But across locales, timezones, and discrete days-per-month counts, it can get tricky.
Consider what happens when adding 1 month to January 31st––the result depends:
January 31 + 1 month → February 28 (not February 31)
October 31 + 1 month → November 30 (not November 31)
The same date math gives different results based on the starting month.
Now imagine server-side code adding months to dates from user locale contexts globally. Sans careful timezone handling, days could shift incorrectly.
These examples reveal nuances around:
- Normalized days across differing month lengths
- Leap years requiring February 29th
- Timezones moving days when math crosses
Robust implementations must handle all these scenarios correctly.
Approaches to Adding Months in JavaScript
When adding months in JavaScript, we have two main approaches:
- Native
Date
methods likesetMonth()
- Helper libraries like Moment.js or date-fns
For straightforward use cases, native Date
methods suffice:
// Add 5 months
const date = new Date();
date.setMonth(date.getMonth() + 5);
But for advanced use cases, utilities like Moment.js encapsulate complex edge cases behind a simpler API:
const laterDate = moment().add(5, ‘months‘);
Let‘s explore native and library techniques for reliably adding months.
Adding Months with Native JavaScript Date Methods
The native JavaScript Date
object provides a setMonth()
method for adding months to dates.
Example: Add 5 Months
// Create base date
const date = new Date(‘2023-03-25‘);
// Add 5 months using setMonth()
date.setMonth(date.getMonth() + 5);
console.log(date);
// > Fri Aug 25 2023 00:00:00
Here‘s how it works:
- Create a
Date
instance representing the starting date - Call
.getMonth()
to get the 0-indexed current month value - Add the number of months to increment by
- Pass the new month number into
.setMonth()
This approach is simple but misses some edge cases we must address…
Normalizing Days Across Months
A complexity arises when adding months across month boundaries with different day counts.
For example:
January 31 + 1 month → February 28
August 31 + 1 month → September 30
The original day gets normalized to the last day of the new month.
We can force retaining the original day by extracting it and re-setting the date after adding months:
function addMonths(date, monthsToAdd) {
const day = date.getDate();
date.setMonth(date.getMonth() + monthsToAdd);
if (date.getDate() !== day) {
date.setDate(day);
}
return date;
}
Now the exact day is unchanged, even when shifting across months.
Handling Leap Years Properly
Another consideration is leap years that have 29 days in February.
When adding months across February 29th in a leap year, the logic must retain February 29th rather than normalizing to February 28th:
const date = new Date(2020, 1, 29); // Feb 29, 2020
addMonths(date, 12);
// Should keep Feb 29 instead of normalizing to Feb 28
Detecting leap years takes some additional logic but prevents unwanted date normalization:
function isLeapYear(year) {
return year % 100 === 0 ? year % 400 === 0 : year % 4 === 0;
}
function addMonths(date, months) {
if (isLeapYear(date.getFullYear())) {
// Handle Feb 29 properly
}
date.setMonth(date.getMonth() + months);
return date;
}
This protects February 29th from being incorrectly shifted during month math in leap years.
Avoiding Timezone Day Shifts
One last timezone nuance occurs when adding months to dates.
For example, March 31st becomes April 30th when adding a month in New York. But for timezones west still on March 31st, it incorrectly shifts the day to March 30th.
We can eliminate this by consistently operating on UTC dates using .toISOString()
:
function addMonths(date, months) {
// Convert to UTC date string
date = new Date(date.toISOString());
date.setMonth(date.getMonth() + months);
return date;
}
Now date math ignores local timezones and works consistently across server or client contexts.
Validating Inputs
For safety, we should also validate parameters passed to our addMonths
function:
function addMonths(months, date) {
if (!isValidNumber(months)) {
// Throw exception
}
if (!(date instanceof Date)) {
// Throw exception
}
date.setMonth(date.getMonth() + months);
return date;
}
This guards against bugs by failing fast for invalid data types or values.
Abstracting Dates with Helper Libraries
For more complex use cases, abstracting dates behind libraries like Moment.js simplifies the API:
import moment from ‘moment‘;
// Add 5 months
const laterDate = moment().add(5, ‘months‘);
This handles all the edge cases behind the scenes and exposes a readable interface.
Let‘s contrast native and library approaches…
Native Dates vs Helper Libraries
Feature | Native Date | Moment.js |
---|---|---|
Lines of Code | More | Less |
Readability | Verbose | Cleaner |
Edge Cases | Developer Must Handle | Built-in Handling |
IE Support | IE6+ | IE9+ |
Learning Curve | Lower | Higher |
If readability/speed are priorities, libraries like Moment simplify adding months (and other date logic).
But native methods require no external dependencies.
Other Popular Date Libraries
Beyond Moment, several date manipulation libraries exist:
- date-fns – Lightweight module with custom building
- Day.js – Immutable Date utility with chainable API
- date-and-time – Timezone helper tools
- luxon – Modern API modeled after Moment
All encapsulate tough date logic for easier usage. But native methods integrate out of the box.
Use Cases for Adding Months to Dates
Now that we‘ve explored techniques for adding months, what are some real-world use cases?
Calendars and Scheduling
Calendar apps need to increment months to move ahead or back for navigation. Adding months to the currently displayed month enables building calendars with any timeframe.
Recurring events also rely on date math to calculate future occurrences from a starting date.
// Calculate every 3rd Monday from March 2023
const event = {
start: new Date(2023, 2, 20), // March 20
frequency: 3,
weekDay: 1 // Monday
}
function getNextEventDate(event) {
let date = new Date(event.start);
date = addMonths(date, event.frequency);
// Normalize date to third week and Monday
date = normalizeToWeekAndDay(date, 3, 1)
return date;
}
// Now returns June 19, 2023 (third Monday in June 2023)
This powers recurring calendar events, meetings and reminders.
Billing and Invoicing
For subscription billing and invoicing, its common to charge customers each month.
Date math that increments months can create future invoice due dates from a starting invoice or sign update date.
// Generate 12 monthly invoices from start date
function createInvoice(startDate) {
const invoices = [];
for (let i = 0; i < 12; i++) {
const dueDate = addMonths(startDate, i);
invoices.push({
due: dueDate // Increment by month
});
}
return invoices;
}
This builds scheduled invoices by adding months as each iteration occurs.
Financial Projections
In finance, its often necessary to know dates in the future for accounting, budgeting, or predicting cash flow over time.
By consistently adding months relative to the current date, projected values can populate future months for analysis:
2023 2024
March $34,000
+ 1 Month → April $32,000
+ 1 Month → May $31,000
Date math enables building financial models for any timeframe needed.
Best Practices for Adding Months in JavaScript
When adding months to JavaScript dates, follow these best practices:
Use Utility Functions
Encapsulate date math in separate utility functions for clean code:
// Good
addMonths(date, 5);
// Not ideal
date.setMonth(date.getMonth() + 5);
Parse ISO Date Strings
Operating on ISO-formatted strings eliminates timezone inconsistencies.
Preserve Days
Detect when days shift across months and retain original day.
Consider Leap Years
Handle February 29th properly during month math in leap years.
Validate Inputs
Guard against invalid data that could break math and cause exceptions.
Use Libraries to Abstract Complexity
For advanced use cases, leverage Moment or date-fns to simplify month handling behind a clean interface.
Conclusion
Adding months to JavaScript dates powers dynamic applications requiring temporal capabilities.
Using native Date
methods or external libraries, we can achieve robust month math––but some important nuances exist around days, timezones, leap years, and invalid data.
By applying the best practices outlined here for input validation, day preservation, timezone standardization and encapsulation in utilities, adding months can handle every edge case.
For uncompromising date math, native Date methods take some extra care––but battle-tested libraries like Moment.js abstract this complexity behind an elegantly simple interface.
No matter the approach, mastering the techniques covered empowers building dynamic calendars, intelligent financial models, powerful invoicing engines and other innovative applications requiring fluid date manipulation in JavaScript.