Stop miscalculating age in JavaScript: leap years, Feb 29, and the Jan 31 trap
Source: Dev.to
Most age calculators get it wrong
- They simply do
nowYear - dobYearand forget to check if the birthday has already happened. - They treat all months as having the same length.
- They explode on Feb 29 birthdays.
- They hit JavaScript’s “Jan 31 + 1 month = March” surprise.
If you need an age calculator you can ship, you have to handle these edge cases correctly.
What we’re actually trying to compute
Given
dob– date of birthasOf– the date you want to measure age on (defaults to today)
We want
- years – full birthdays completed
- months – full months since the last birthday
- days – remaining days since that month anchor
If asOf = dob);
// ---- Years ----
let years = asOf.getFullYear() - dob.getFullYear();
const bdayThisYear = birthdayInYear(dob, asOf.getFullYear(), "FEB_28");
if (asOf {
it("handles birthday not yet happened this year", () => {
const dob = new Date("2000-10-20");
const asOf = new Date("2026-02-09");
const r = calculateAge(dob, asOf);
expect(r.years).toBe(25);
});
it("handles month‑end clamping (Jan 31)", () => {
const dob = new Date("2000-01-31");
const asOf = new Date("2000-03-01");
const r = calculateAge(dob, asOf);
// If your month add is buggy, this often breaks.
expect(r.years).toBe(0);
expect(r.months).toBeGreaterThanOrEqual(1);
});
it("handles Feb 29 birthdays with FEB_28 rule", () => {
const dob = new Date("2004-02-29");
const asOf = new Date("2025-02-28");
const r = calculateAge(dob, asOf);
// Under FEB_28 policy, birthday is considered reached on Feb 28.
expect(r.years).toBe(21);
});
it("rejects asOf before dob", () => {
const dob = new Date("2020-01-01");
const asOf = new Date("2019-12-31");
expect(() => calculateAge(dob, asOf)).toThrow();
});
});
Additional edge cases you can add
dob = 1999-12-31,asOf = 2000-01-01dob = 2000-02-28,asOf = 2001-02-28dob = 2000-03-31,asOf = 2000-04-30
The takeaway
To get correct age output:
- Normalize to date‑only (midnight).
- Define a consistent Feb 29 policy.
- Clamp month ends when stepping through months.
- Ship tests that cover the weird dates.
That’s it—no external libraries required.
Demo (optional):