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));
}
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:
- Leap years don't distribute uniformly — they cluster every 4 years
- Someone born January 1, 2000 is not quite 26.25 years old on March 24, 2026
- 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
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)
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
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
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 };
}
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)