Skip to content

Methodology

Age calculation methodology

Calendar-aware borrowing, not naive division. The 4-day error that naive code accumulates.

Computing age in years-months-days sounds trivial. It isn’t. The naive “today minus birthday divided by 365.25” approach drifts about one day every four to seven years because real years aren’t a constant number of days. The correct algorithm uses calendar borrowing — same logic as elementary-school subtraction but with variable-length “digits.”

The borrow-from-month algorithm

Given a birth date (bY-bM-bD) and today (tY-tM-tD):

  1. Year diff: tY − bY.
  2. Month diff: tM − bM. If negative, borrow 1 from the year count: subtract 1 from year diff, add 12 to month diff.
  3. Day diff: tD − bD. If negative, borrow 1 from the month count: subtract 1 from month diff, add the length of the previous month to day diff.

Step 3 is where most implementations go wrong. The “previous month’s length” isn’t a constant 30 — it’s 28, 29, 30, or 31 depending on which month and (for February) which year.

Worked example

Birth: 1990-12-31. Today: 2026-03-15.

  1. Year diff: 2026 − 1990 = 36.
  2. Month diff: 3 − 12 = −9. Borrow: year diff drops to 35, month diff becomes 3.
  3. Day diff: 15 − 31 = −16. Borrow: month diff drops to 2, day diff += previous month’s length. Previous month is February 2026 (28 days, not a leap year). Day diff = −16 + 28 = 12.

Result: 35 years, 2 months, 12 days.

The previous-month-length trick

The cleanest way to ask “how long was the previous month?” in JavaScript:

new Date(year, month, 0).getDate()
// Returns the last day of (month - 1).
// Day 0 of month N is interpreted as day -0 = last day of N-1.

This handles February leap-year correctness automatically because Date knows. new Date(2024, 2, 0).getDate() returns 29; new Date(2023, 2, 0).getDate() returns 28.

Leap-year birthdays (Feb 29)

People born on February 29 raise a recurring question: what’s their birthday in non-leap years? Two conventions:

  • Roll to March 1 — UK, Hong Kong default.
  • Roll to February 28 — US, Taiwan, New Zealand default. The calculator uses this.

The choice affects when the “you turned N” moment happens in non-leap years. For most purposes it doesn’t matter — the underlying birth date is still Feb 29 and the difference is one day per non-leap year.

Why “days / 365.25” drifts

365.25 is the averagelength of a year over a 400-year Gregorian cycle. Any individual year is 365 or 366. Over short windows the average isn’t a good approximation:

  • 4 years contain exactly 1 leap year → 1461 days → 365.25 exactly. Good.
  • 3 years from 1-Mar-2021 → 1095 days. Divided by 365.25 = 2.998… → rounded, 3 years. Correct.
  • 3 years from 1-Mar-2020 → 1096 days (includes Feb 29 2024 wait — no, doesn’t include it). Still 1095 → same.
  • The drift comes from the century rule: 1900 was not a leap year (divisible by 100, not by 400). 2000 was. Computing ages spanning 1900 or 2100 with the 365.25 approximation produces visible day-level errors.

For age calculation, calendar borrowing is always exact. The naive approximation is fine for back-of-envelope but wrong for anything that has to match a passport or birth certificate.

Edge cases the calculator handles

  • Future birth dates — we return zero rather than negative ages. A negative age is a bug surface.
  • Today equals birth date — 0 years, 0 months, 0 days.
  • Across DST transitions— the calculator uses date-only math (no times), so DST changes don’t affect the result.

Related

Published May 15, 2026