What is January 31 plus one month? The end-of-month rule
Under the rule used by Excel's EDATE, most date libraries and most contracts, January 31 plus one month is the last day of February — February 28, or February 29 in a leap year. The day-of-month is kept when it exists in the target month and otherwise moved back to that month's last day. Some tools behave differently, though: plain JavaScript rolls over to early March, and some programs raise an error instead. The date calculator uses the end-of-month rule and tells you whenever it applied it. Here is why the problem exists and how each tool handles it.
Why months break simple arithmetic
Days and weeks are fixed lengths: a week is always 7 days, so "add 2 weeks" is unambiguous. Months are not. They run 28, 29, 30 or 31 days, and "one month later" is normally understood as "the same day number next month" rather than "30 days later". That works until the day number doesn't exist in the target month. March has a 31st; April doesn't. So what is March 31 plus one month? There are three defensible answers:
- Clamp to the end of the month: April 30. The result stays in the intended month.
- Overflow into the next month: "April 31" doesn't exist, so count one day past April 30 and get May 1.
- Refuse: treat it as an error and make the caller decide.
Clamping is the most common convention in finance, law and spreadsheets because it preserves the meaning of "monthly": a payment due "one month after" January 31 should be due in February, not in March.
How popular tools behave
| Tool | Expression | Jan 31, 2025 + 1 month |
|---|---|---|
| Excel / Google Sheets | =EDATE(date, 1) | Feb 28, 2025 (clamped) |
| Excel / Sheets naive | =DATE(YEAR(d), MONTH(d)+1, DAY(d)) | Mar 3, 2025 (overflow) |
JavaScript Date | d.setMonth(d.getMonth() + 1) | Mar 3, 2025 (overflow) |
Python dateutil | d + relativedelta(months=1) | Feb 28, 2025 (clamped) |
| Python standard library | d.replace(month=2) | ValueError (refuses) |
Java java.time | d.plusMonths(1) | Feb 28, 2025 (clamped) |
| PostgreSQL | date + interval '1 month' | Feb 28, 2025 (clamped) |
| This calculator | + 1 month | Feb 28, 2025 (clamped, with a note) |
The JavaScript row explains a bug developers keep rediscovering: code that adds a month with
setMonth works for 28 days of every month and then jumps to March for end-of-January dates.
It's a classic example of a bug that only appears when comparing two languages' output side by side, or
on the 31st in production. If you write JavaScript, either clamp manually (compute the target month's
length and take the smaller day) or use a library or the newer Temporal API, whose
PlainDate.add clamps by default.
Clamping is not reversible
Clamped month arithmetic has a property that surprises people: adding and then subtracting a month does not always get you back where you started. January 31 + 1 month = February 28; February 28 − 1 month = January 28. Three days have quietly disappeared. Likewise, adding one month twice is not the same as adding two months: January 31 + 1 month + 1 month = March 28, but January 31 + 2 months = March 31.
The practical lesson for anything recurring — rent, subscriptions, loan repayments — is to compute each occurrence from the original anchor date (start + 1 month, start + 2 months, start + 3 months) rather than from the previous occurrence. Otherwise a schedule anchored on the 31st drifts to the 28th after February and stays there forever.
Leap years and February 29
The same rule covers years. February 29, 2024 plus one year is February 28, 2025 under clamping, because 2025 has no February 29. Some systems overflow to March 1 instead, and for legal ages the answer depends on jurisdiction — some treat a person born on February 29 as having a birthday on February 28 in common years, others on March 1. Leap years themselves follow the Gregorian rule: every year divisible by 4, except century years, unless the century year is divisible by 400. So 2000 was a leap year, 2100 will not be.
Order of operations with mixed units
When you add "1 month and 1 day", the result can depend on the order. Starting on January 31, 2025:
- Month first: January 31 → February 28 → March 1.
- Day first: January 31 → February 1 → March 1.
Those happen to agree, but from January 30 they don't: month first gives February 28 + 1 day = March
1, while day first gives January 31 + 1 month = February 28. The widely used convention — followed by
ISO 8601 durations, java.time, dateutil and this calculator — is to apply the
largest units first: years, then months, then weeks and days. If you need a different order, do the
steps one at a time.
Months versus 30 days
When a rule says "30 days", count 30 days; when it says "one month", use calendar months. They give different results most of the time:
| Start | + 1 month | + 30 days |
|---|---|---|
| Jan 15, 2026 | Feb 15, 2026 | Feb 14, 2026 |
| Feb 15, 2026 | Mar 15, 2026 | Mar 17, 2026 |
| Apr 15, 2026 | May 15, 2026 | May 15, 2026 |
| Jan 31, 2026 | Feb 28, 2026 | Mar 2, 2026 |
Financial products sometimes use a third option, a "30/360" day-count convention, which pretends every month has 30 days for the purpose of calculating interest. That convention is about measuring interest, not about finding a calendar date, so it doesn't change when a payment falls due.
Counting the months between two dates
The inverse question — how many months between two dates — uses the same logic. The calculator steps
forward whole months from the start date (with clamping) for as long as it doesn't pass the end date,
then counts the leftover days. From January 31, 2024 to March 1, 2024 that gives 1 month and 1 day
(January 31 → February 29 → March 1). Excel's DATEDIF(start, end, "M") gives 1 whole month
for the same pair; its "MD" unit has documented quirks around month ends, which is one reason to
double-check spreadsheet results near the 31st.
Getting it right in practice
- Say which rule you use. In a contract or spec, write "if the corresponding day does not exist, the last day of the month applies" rather than leaving it implicit.
- Anchor recurring dates to the original date, not to the previous occurrence.
- Avoid overflow functions like JavaScript's
setMonthor a naive spreadsheetDATE()for month arithmetic unless overflow is what you want. - Test the 29th, 30th and 31st, and February in both leap and common years. Most month bugs are invisible on the 15th.
- Use whole-day arithmetic. Adding 24-hour blocks across a daylight-saving change can land on 23:00 the previous day, which then displays as the wrong date.
To try any of these cases, enter the date in the calculator: it shows the clamped result, says when clamping occurred, and lets you compare with a plain day count. For questions about whether the first day of a period counts, see inclusive vs exclusive day counting.