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.
- True tropical year (days)
- Gregorian average (days)
- 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 date and current date, but through different pure functions. The logic is compact, fully tested, and runs entirely in your browser.
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.