Micro Tool Yard logo
Tools

Blog

Why Calculating 'Days Between Two Dates' Is Harder Than Subtraction

Calendar months don't have a fixed length, leap years break simple day-counting, and timezones can make the same instant fall on two different calendar dates — subtraction alone doesn't survive contact with any of it.

"How many days between March 1st and June 1st?" looks like it should reduce to one subtraction. It doesn't, because the Gregorian calendar isn't a uniform number line — months have different lengths, some years have an extra day, and "a date" isn't even a well-defined single instant once timezones enter the picture. Each of these turns what looks like arithmetic into a small lookup problem.

Months aren't a fixed unit

A day is a day and a year is (almost) always the same length, but a month ranges from 28 to 31 days depending on which one it is, so "add one month" isn't a fixed offset the way "add one day" is — it's a rule that has to consult a calendar table. That's fine until you hit the edge case every date library has to special-case explicitly: what is "one month after January 31st"? February doesn't have a 31st, so the honest answers are either "February 28th" (or 29th) or "March 3rd" (spilling the extra days into the next month), and different systems genuinely disagree — spreadsheet software, programming language date libraries, and financial systems don't all pick the same convention. This is precisely why "add N months" is a documented source of off-by-a-few-days bugs in billing systems: a subscription that renews "one month" after January 31st behaves differently depending on which convention the billing engine happens to use, and nobody notices until an invoice looks wrong in March.

Leap years, and the exception to the exception

A year is 365 days, except when it's 366, and the rule for which years get the extra day is itself a small nested exception: a year is a leap year if divisible by 4, unless it's also divisible by 100, unless it's also divisible by 400. So 2000 was a leap year (divisible by 400), but 1900 wasn't (divisible by 100, not 400), even though both are divisible by 4. This exists because a solar year is actually about 365.2422 days, not 365.25, so the simple "every 4 years" rule overcorrects slightly, and the century exceptions claw back most of that overcorrection. Any date-difference calculation spanning February 29th of a leap year has to know this rule correctly or it'll be off by exactly one day — small, but often exactly the kind of off-by-one that turns "you'll turn 30 on your birthday" into "you'll turn 30 the day before," which is a genuinely common bug in hand-rolled age calculators that just divide total days by 365.

Timezones: when "today" isn't the same day everywhere

A date isn't a universal marker — it's tied to a location's local clock. When it's 11:00 PM on June 1st in New York, it's already 3:00 AM on June 2nd in London, meaning the same instant in time corresponds to two different calendar dates depending on where you're standing. This matters enormously for anything computed from a timestamp rather than a plain date: a system that stores "created at" as a UTC timestamp and then displays "days since creation" without converting to the user's local timezone first can report an off-by-one day count for users far enough from UTC, purely because midnight fell in a different place than the calculation assumed. It's a common source of subtle date-math bugs in web applications — the underlying number (a Unix timestamp) is unambiguous, but the calendar date it maps to genuinely isn't, until a timezone is specified.

Business-day counting adds a second calendar on top of the first

Once weekends and holidays enter the question — "how many business days until this contract is due" — the problem stops being pure calendar math and becomes a lookup against a second, separate calendar: which days count as non-working. Weekends are at least predictable (though not universal — some countries use a Friday-Saturday weekend instead of Saturday-Sunday), but public holidays vary by country, by region within a country, and by year, since holidays like Easter or Thanksgiving move around the calendar or get observed on a shifted date when they'd otherwise fall on a weekend. A "days between" calculation that needs to be holiday-aware isn't solvable from the two dates alone — it requires an external table of which specific days are excluded, which is a fundamentally different kind of computation than counting days on a calendar.

What this means for a straightforward date calculation

None of this makes date math impossible — it just means "days between two dates" is really "days between two dates, given a specific calendar system, leap-year rule, and timezone convention," and skipping that specification is where the bugs live. Converting both dates to a single unambiguous representation (a day count from a fixed epoch, in a single timezone) and subtracting handles the leap-year and month-length issues correctly by construction, since the day count itself already accounts for them — which is the approach behind thedate and age calculator on this site, rather than trying to reason about months and years as if they were uniform units.