DEV Community

HAU
HAU

Posted on

How to Calculate Age Correctly: The Edge Cases Every Developer Misses

Calculating someone's age sounds like a five-minute task:

function getAge(birthday) {
  const diff = Date.now() - new Date(birthday).getTime();
  return Math.floor(diff / (365.25 * 24 * 60 * 60 * 1000));
}
Enter fullscreen mode Exit fullscreen mode

Ship it. Except this gives wrong answers on certain dates and can be off by a full year around birthdays. Let's fix it.

Why the Division Approach Fails

Dividing milliseconds by an "average year" (365.25 days) is imprecise because:

  1. Leap years don't distribute uniformly — they cluster every 4 years
  2. Someone born January 1, 2000 is not quite 26.25 years old on March 24, 2026
  3. The rounding can flip in the wrong direction around the birthday
// Test: birthday Jan 1, today March 24 2026
getAge('2000-01-01'); // Should be 26, might return 25 or 26 depending on leap year timing
Enter fullscreen mode Exit fullscreen mode

The Correct Calendar-Based Approach

Age in years is a calendar concept, not a duration in milliseconds. Calculate it as a calendar difference:

function getAge(birthdayStr) {
  const today = new Date();
  const birth = new Date(birthdayStr);

  let age = today.getFullYear() - birth.getFullYear();

  // Has the birthday occurred yet this year?
  const monthDiff = today.getMonth() - birth.getMonth();
  const dayDiff = today.getDate() - birth.getDate();

  if (monthDiff < 0 || (monthDiff === 0 && dayDiff < 0)) {
    age--; // Birthday hasn't happened yet this year
  }

  return age;
}

getAge('2000-01-01'); // 26 ✓ (birthday has passed)
getAge('2000-12-25'); // 25 ✓ (birthday hasn't happened yet in 2026)
Enter fullscreen mode Exit fullscreen mode

The Leap Year Birthday Problem

People born on February 29 have a birthday that doesn't exist in most years. Different jurisdictions have different legal answers:

  • UK: Leap day birthday → legal age milestone on March 1 in non-leap years
  • New Zealand, Hong Kong: Legal age milestone on February 28 in non-leap years
  • Taiwan: March 1 in non-leap years
  • US: Not federally specified; varies

For an application context (not a legal one), February 28 is the safer default:

function getAge(birthdayStr) {
  const today = new Date();
  const birth = new Date(birthdayStr);

  let age = today.getFullYear() - birth.getFullYear();

  // Normalize Feb 29 to Feb 28 in non-leap years for comparison
  let birthThisYear = new Date(
    today.getFullYear(),
    birth.getMonth(),
    birth.getDate()
  );

  // If Feb 29 doesn't exist this year, setDate(29) rolled over to Mar 1
  // setDate(0) instead gets us Feb 28
  if (birth.getMonth() === 1 && birth.getDate() === 29) {
    const isLeapYear = new Date(today.getFullYear(), 1, 29).getDate() === 29;
    if (!isLeapYear) {
      birthThisYear = new Date(today.getFullYear(), 1, 28);
    }
  }

  if (today < birthThisYear) age--;

  return age;
}

getAge('2000-02-29'); // Correct in both leap and non-leap years
Enter fullscreen mode Exit fullscreen mode

Displaying Age with Full Precision

For contexts like a baby's age or medical records, years alone aren't enough. A "6-month-old" is meaningfully different from a "1-year-old":

function getDetailedAge(birthdayStr) {
  const today = new Date();
  const birth = new Date(birthdayStr);

  let years = today.getFullYear() - birth.getFullYear();
  let months = today.getMonth() - birth.getMonth();
  let days = today.getDate() - birth.getDate();

  if (days < 0) {
    months--;
    // Days in the previous month
    const prevMonthEnd = new Date(today.getFullYear(), today.getMonth(), 0);
    days += prevMonthEnd.getDate();
  }

  if (months < 0) {
    years--;
    months += 12;
  }

  return { years, months, days };
}

getDetailedAge('2000-01-15');
// { years: 26, months: 2, days: 9 } on March 24, 2026
Enter fullscreen mode Exit fullscreen mode

Next Birthday Calculation

function nextBirthday(birthdayStr) {
  const today = new Date();
  const birth = new Date(birthdayStr);

  let nextBday = new Date(
    today.getFullYear(),
    birth.getMonth(),
    birth.getDate()
  );

  // Handle Feb 29 in non-leap year
  if (birth.getMonth() === 1 && birth.getDate() === 29) {
    const year = nextBday.getMonth() === 2 ? today.getFullYear() + 1 : today.getFullYear();
    nextBday = new Date(year, 1, 28); // Use Feb 28 as approximation
  }

  // If birthday already passed this year, use next year
  if (nextBday <= today) {
    nextBday.setFullYear(today.getFullYear() + 1);
  }

  const daysUntil = Math.ceil((nextBday - today) / 86400000);
  return { date: nextBday, daysUntil };
}
Enter fullscreen mode Exit fullscreen mode

Zodiac Signs: An Unexpected Date Edge Case

If you're building a birthday-related feature that includes zodiac signs, note that signs depend on the exact day — and the boundaries shift by a day between years due to the solar year not being exactly 365 days. Hard-coding "Aries starts March 21" will be wrong some years.

Quick Age Check Without Code

For manual verification or quick checks, datetimecalculator.app/age-calculator shows precise age in years, months, days, and even hours and seconds — plus the next birthday countdown. Useful for validating your implementation against a known-good result.

Summary

  • Never calculate age by dividing milliseconds — use calendar arithmetic
  • Check whether the birthday has occurred yet in the current year
  • Feb 29 birthdays need explicit handling; define your non-leap-year convention
  • For medical or legal applications, store the full birthdate and always recalculate age at display time — never store a cached age value

Have you ever shipped an age calculation bug? The leap birthday ones tend to stay hidden for exactly 4 years.

Top comments (0)