Skip to content
T
Tools.Town
Free Online Tools for Everyone
Date And Time

Date Math, Explained: Why Your Age in Days Isn't Years × 365

Naive date arithmetic breaks in three predictable places: leap years, end-of-month rollovers, and the day-before-birthday edge case. Here's the math that fixes each, with worked examples from our Age in Days tool.

16 May 2026 4 min read By Tools.Town Team Fact Checked

Key Takeaways

  • On average, 365
  • Days are uniform — every day is exactly 86,400 seconds (ignoring leap seconds)
  • Not for this tool
  • It means the algorithm respects month and year boundaries instead of just dividing the millisecond delta by 30 or 365

Why we even need date math

“Born 16 May 1996. What is today’s age?” sounds trivial. It is, until you write the obvious code:

const years = now.getFullYear() - birth.getFullYear();  // wrong on day-before-birthday
const days = (now - birth) / (1000 * 60 * 60 * 24);     // works! but only for days
const months = years * 12 + (now.getMonth() - birth.getMonth());  // off by 1 in mid-month

Each of these is wrong in a different way, and none of the failures are obvious until a user reports it. Tools that get date math right (ours included) handle three specific traps.

The three traps

Trap 1: Leap years

The Gregorian calendar inserts a leap day every 4 years, except on century years not divisible by 400. The rule:

isLeapYear(year) = (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0)

So 2000 was a leap year (divisible by 400) but 1900 was not (divisible by 100, not 400). Modern code rarely hits this edge — JS Date handles it — but it explains why “years × 365” undercounts by 7–8 days over a 30-year lifespan.

365.2422
True tropical year (days)
365.2425
Gregorian average (days)
~30 sec
Drift per year vs reality

Trap 2: End-of-month rollover

Born Jan 31. Today is Feb 28 (non-leap). How many months old are you?

Naive code says: now.getMonth() - birth.getMonth() = 1. But you haven’t lived through a full month — you’d hit your “monthly birthday” again on Mar 31, not Feb 31 (which doesn’t exist). The right answer is 0 months, 28 days.

The fix: check the day-of-month first. If now.getDate() < birth.getDate(), you haven’t “completed” the month yet — subtract 1 from the months field and borrow the days from the previous month’s length.

let months = now.getMonth() - birth.getMonth();
let days = now.getDate() - birth.getDate();
if (days < 0) {
  months -= 1;                                       // borrow from months
  const prev = new Date(now.getFullYear(), now.getMonth(), 0);
  days += prev.getDate();                            // days in the previous month
}

This is the exact pattern our Age in Days tool uses, and it’s directly testable: born Jan 31 2020, today Feb 28 2020 → 0 years, 0 months, 4 weeks, 0 days. Try it.

Trap 3: The day-before-birthday bug

Born May 16 1996. Today is May 15 2026 — the day before your 30th birthday. Naive code:

const years = 2026 - 1996;  // 30 — but you're not 30 yet!

The fix follows directly from Trap 2: if you haven’t passed your birthday this year (months negative OR same month but days negative), subtract 1 from years. Most date libraries roll this in automatically; if you’re writing it from scratch, don’t skip it.

Many jurisdictions define “age” as completed years on the relevant date. Get this wrong in a KYC, voting-eligibility, or alcohol-age check and you’ll either let in 17-year-olds or block 18-year-olds on their birthday. Always use a tested library or our Age Calculator — never currentYear - birthYear.

Why elapsed-days math is so much simpler

If you only care about how many days (not “how many years and months”), the math is one line:

const days = Math.floor((now.getTime() - birth.getTime()) / 86_400_000);

That’s it. Days have a fixed length (modulo daylight-saving boundaries, which JavaScript’s Date hides from you). Leap years happen inside the millisecond delta, automatically.

This is why the Age in Days tool has two views side-by-side:

  • Elapsed units (days, hours, minutes, seconds) — fixed-length, trivial math.
  • Calendar breakdown (years, months, weeks, days) — variable-length, the math you just read about.

Both are computed from the same birth and now, but through different functions in our open-source age-in-days.ts. Take a look — it’s 80 lines, fully tested, and runs anywhere JavaScript runs.

Edge cases that surprise people

”I was born on Feb 29 — what’s my age on a non-leap year?”

Convention: your year increments on Mar 1. Most legal systems, most date libraries, and our tool all agree. The day-of-month comparison in Trap 2’s code handles it correctly — on Feb 28 (the day before your “March birthday”), you’re not a year older yet; on Mar 1, you are.

”Daylight-saving moved my birthday by an hour”

Don’t worry about it. JavaScript’s Date uses millisecond timestamps under the hood, which are insensitive to DST. The displayed time-of-day might shift by an hour twice a year, but the day you were born on doesn’t change. The tool stores the birth date as a date-only YYYY-MM-DD string, parses it to local midnight, and computes elapsed time in milliseconds — so DST never enters the math.

”What about time zones?”

The tool treats your birth date and “now” in the same timezone — the one your browser is running in. If you crossed timezones (say, born in India, now living in Sydney), the day count is your local-Sydney day count from your local-India birth midnight. For most uses that’s right; for forensic-precision date math, you’d need to specify which timezone counts as your “home” one. That’s a richer date library’s job.

Use the tool, see the math

The Age in Days tool puts all of this on one page. The live ticker shows you the elapsed view; the four tiles below show the calendar view. Both run client-side from a single tested function. Try the day-before-birthday case — it’s the bug most age calculators on the web still get wrong.

Advertisement

Try Age in Days/Hours — Free

Apply what you just learned with our free tool. No sign-up required.

Try Age in Days/Hours

Frequently Asked Questions

How many days are in a year, exactly?
On average, 365.2422 — the tropical year. The Gregorian calendar rounds to 365 with a leap day every 4 years, skipping the leap on century years not divisible by 400 (so 1900 was NOT a leap year, but 2000 WAS). Over a long enough span, this averages to 365.2425, which is within 30 seconds per year of the true tropical year.
Why does 'months between dates' have ambiguity that 'days between dates' doesn't?
Days are uniform — every day is exactly 86,400 seconds (ignoring leap seconds). Months aren't: February has 28 or 29 days, others have 30 or 31. Subtracting 'month numbers' gives different real-world durations depending on which months you cross. That's why every date library exposes 'days between' as a single integer but 'months between' as a struct of years/months/days.
Are leap seconds a problem here?
Not for this tool. Leap seconds (the occasional extra second added to UTC to keep it aligned with Earth's rotation) are handled by your OS clock, and JavaScript's Date treats time as a smooth Unix-epoch counter that ignores them. Over a 30-year lifespan you'd accumulate ~25 leap seconds — less than half a minute, invisible at the day-level resolution this tool cares about.
What does 'calendar-aware' actually mean in code?
It means the algorithm respects month and year boundaries instead of just dividing the millisecond delta by 30 or 365. Concretely: if you were born on Jan 31 and today is Feb 28, the 'months' field is 0 (not 1), because you haven't lived through a full Feb→Feb cycle yet.

Was this guide helpful?

Your feedback helps us improve our content.

Get the best Date And Time tips & guides in your inbox

Join 25,000+ users who get our weekly date and time insights.