Skip to content
100% in your browser. Nothing you paste is uploaded — all processing runs locally. Read more →

Inclusive or exclusive? The off-by-one in date counting that costs people money

5 min read #date #edge cases #contracts

Quick: a 30-day free trial starts on April 1. When does it end?

It depends on who you ask.

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:

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:

Inclusive is the right answer for:

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:

DomainConvention
API expiryExclusive (expires_at = now + 30d)
Contract / lease termInclusive of both signing and last day
Free trialInclusive of signup day, exclusive of expiry day
Conference / event durationInclusive of both endpoints
Days since XExclusive
Vacation accrualInclusive of both endpoints
Travel itineraryInclusive of arrival and departure days
Hospital LOSInclusive (both endpoints count)
Project / PM scheduleInclusive 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”:

That difference of 1 has cost real money in real systems. Pick the right mode for your contract; document it; don’t switch silently.