Stop miscalculating age in JavaScript: leap years, Feb 29, and the Jan 31 trap

Published: (February 8, 2026 at 08:18 PM EST)
2 min read
Source: Dev.to

Source: Dev.to

Most age calculators get it wrong

  • They simply do nowYear - dobYear and 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 birth
  • asOf – 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-01
  • dob = 2000-02-28, asOf = 2001-02-28
  • dob = 2000-03-31, asOf = 2000-04-30

The takeaway

To get correct age output:

  1. Normalize to date‑only (midnight).
  2. Define a consistent Feb 29 policy.
  3. Clamp month ends when stepping through months.
  4. Ship tests that cover the weird dates.

That’s it—no external libraries required.

Demo (optional):

0 views
Back to Blog

Related posts

Read more »

Hello World

Hello I am a new web developer, I am looking for new ways to improve as a web developer. If we have any problems connecting what we've learned please contact me...