Inclusive or exclusive? The off-by-one in date counting that costs people money
Quick: a 30-day free trial starts on April 1. When does it end?
It depends on who you ask.
- Software defaults: subtract 30 from May 1 → trial ends April 30
inclusive (last day of access). Or, if your trial is implemented as
expires_at = start + INTERVAL '30 days', it ends at midnight UTC of May 1 → access ends April 30 23:59 UTC. Nearly the same answer. - Common reading by users: “30 days from April 1” → 30 calendar days, including April 1 → ends April 30.
- Lawyer-style strict reading: “30 days from” usually means 30 days after, exclusive of the starting day → ends May 1.
- Prison sentencing math: “30 days” usually counts both the reception day and the release day → ends April 30, but you spent 30 separate days inside (April 1 + 29 nights = 30 days).
Three close-but-different answers. Almost every domain that “counts days” has a convention, and most software gets it wrong somewhere.
The two definitions
Exclusive count (also called “delta” or “difference”):
days = end - start
This is what (b - a) / 86_400_000 gives you in JavaScript. It’s the
number of midnights crossed. April 1 → April 2 is 1 day.
Inclusive count (also called “calendar days” or “elapsed days”):
days = (end - start) + 1
This counts both endpoints. April 1 → April 2 is 2 days.
Neither is wrong. They answer different questions:
- Exclusive: “How many days have passed between these two dates?”
- Inclusive: “How many calendar days does this span cover?”
For the first, the date calculator defaults to exclusive. For the second, tick “Include the end date.”
Where each one is correct
Exclusive is the right answer for:
- Time elapsed since an event (“days since last incident”)
- Age (you turn 30 the day after your 30th birthday-day-count, sort of)
- API contracts (“free trial expires 30 days from signup”)
- Most mathematical reasoning
Inclusive is the right answer for:
- Vacation balances (“you have 14 paid vacation days”) — both your first and last day are paid
- Hospital length-of-stay (“admitted for 5 days”) — the day you’re admitted and the day you’re discharged both count
- Conference / event durations (“the conference runs Mon–Wed”) — 3 days, not 2
- Tour / travel itineraries (“a 7-day cruise”) — 7 calendar days, 6 nights
- Prison sentences (under most US state rules) — “30 days” includes both reception and release
- Service-level agreements (“99.9% uptime over 30 days”)
The general rule: if both endpoints are “spent” at the named place, use inclusive. If one endpoint is the boundary you’re crossing (start of trial, deadline), use exclusive.
Where it’s actually been a bug
1. Free trials accidentally giving 31 days
A SaaS computed trial expiry as start_date.replace(day=start_date.day + 30)
rather than start_date + timedelta(days=30). On long months that
mostly worked. On Feb 1, the trial ran until “Feb 31” which silently
became March 3 — 30 calendar days but 32 days of access. Multiple
months later, the company realised they were giving 31-day trials to
users who started in the last days of any month.
Fix: always use proper date arithmetic, never string/integer math on month numbers.
2. Hospital insurance claims off-by-one
A claim system computed length of stay as
discharge_date - admission_date. For a same-day admit/discharge
(common for outpatient procedures), that yielded 0 days, which the
insurance algorithm interpreted as “no inpatient stay” and paid
outpatient rates. Hospitals were undercoding millions of single-day
stays as 0-day stays.
Fix: use inclusive count (+ 1) for length-of-stay calculations.
The unit “1 day” should mean “occupied a bed for 1 calendar day,”
even if the absolute time was less than 24 hours.
3. Vacation tracker losing weekend boundary
An HR system counted vacation days as (end_date - start_date) where
both endpoints were dates the employee was not at work. Friday →
Monday vacation came out as 3, but the employee actually missed
Mon-Fri-Sat-Sun = 1 working day plus 2 weekend days they wouldn’t have
worked anyway. The system charged 3 vacation days instead of 1.
Fix: count business days only (Mon–Fri), inclusive. The date calculator does this if you check “Business days only” + “Include the end date.”
4. Sentencing software miscalculating release dates
In several states, a clerk-error analysis found dozens of cases where sentencing software released defendants 1 day late because the system used exclusive counting (defendant served “30 days = 30 nights” instead of “30 days = 30 calendar days, both endpoints included”). This isn’t theoretical — there are documented appellate court rulings.
Fix: in legal/correctional systems, always default to inclusive counting and document explicitly.
What to actually do
If you’re writing software that takes “X days from Y” as input, ask the user which they mean. Or, if you can’t ask, follow the dominant convention for the domain:
| Domain | Convention |
|---|---|
| API expiry | Exclusive (expires_at = now + 30d) |
| Contract / lease term | Inclusive of both signing and last day |
| Free trial | Inclusive of signup day, exclusive of expiry day |
| Conference / event duration | Inclusive of both endpoints |
| Days since X | Exclusive |
| Vacation accrual | Inclusive of both endpoints |
| Travel itinerary | Inclusive of arrival and departure days |
| Hospital LOS | Inclusive (both endpoints count) |
| Project / PM schedule | Inclusive for “duration,” exclusive for “delta” |
Document the choice. In code:
// Don't:
const days = (end - start) / 86_400_000;
// Do:
function daysBetween(start, end, { inclusive = false } = {}) {
const utcStart = Date.UTC(start.getFullYear(), start.getMonth(), start.getDate());
const utcEnd = Date.UTC(end.getFullYear(), end.getMonth(), end.getDate());
const delta = Math.round((utcEnd - utcStart) / 86_400_000);
return inclusive ? delta + 1 : delta;
}
The required parameter forces every caller to think about which they want.
Try it
The date calculator defaults to exclusive (the developer-natural choice) but has a checkbox to flip it inclusive. Try both modes against “April 1 → April 30”:
- Exclusive: 29 days (29 midnights crossed)
- Inclusive: 30 days (30 calendar days span)
That difference of 1 has cost real money in real systems. Pick the right mode for your contract; document it; don’t switch silently.
Related across the network
- epoch.tooljo.com — when the inputs are Unix timestamps rather than calendar dates, the same off-by-one question shows up but in seconds vs milliseconds form.
- date.tooljo.com/date-format-reference — the cross-format reference. ISO 8601 ranges, RFC 2822, US/EU short forms.
- finance.tooljo.com — contract math (mortgage terms, free trials) is where this off-by-one bites in production most often.