gotchajavascriptMajor
Date arithmetic pitfalls — months, DST, and leap years
Viewed 0 times
date arithmeticaddMonthsDST bugleap yearcalendar arithmetic
Problem
Date arithmetic that looks correct fails on month boundaries, DST transitions, and leap years. Adding 1 month to January 31 via setMonth overflows to March 2.
Solution
Use a library function for calendar arithmetic rather than raw millisecond addition for units larger than hours.
import { addMonths, addYears } from 'date-fns';
addMonths(new Date('2024-01-31'), 1);
// 2024-02-29 (clamps to end of month)
addYears(new Date('2024-02-29'), 1);
// 2025-02-28 (clamps in non-leap year)
import { addMonths, addYears } from 'date-fns';
addMonths(new Date('2024-01-31'), 1);
// 2024-02-29 (clamps to end of month)
addYears(new Date('2024-02-29'), 1);
// 2025-02-28 (clamps in non-leap year)
Why
Months have different lengths. DST adds or removes an hour. Leap years add a day. Millisecond arithmetic ignores all of these.
Gotchas
- Setting month + 1 on Jan 31 overflows to March 2 in plain JS
- Crossing a DST boundary when adding days may produce an off-by-one hour result in local time
- Use Temporal.PlainDate.add({ months: 1 }) for pure calendar (no time) arithmetic
Code Snippets
Month addition gotcha and fix
// WRONG — Jan 31 + 1 month = March 2
const d = new Date('2024-01-31');
d.setMonth(d.getMonth() + 1); // 2024-03-02!
// CORRECT — clamps to end of month
import { addMonths } from 'date-fns';
addMonths(new Date('2024-01-31'), 1); // 2024-02-29Revisions (0)
No revisions yet.