Age Calculator in TypeScript: Calculate Age from Date of Birth
8 min read

Age Calculator in TypeScript: Calculate Age from Date of Birth

Building an age calculator in TypeScript means combining TypeScript’s static types with JavaScript’s date APIs. The safest basic pattern is to treat a birthday as a calendar date, calculate the year difference, then check whether the birthday has occurred in the calculation year. This guide covers completed age, a fixed as-of date, years-months-days, validation, elapsed days, leap years, testing, and a browser-friendly TypeScript example.

TypeScript logo for an age calculator in TypeScript tutorial
Source: Wikimedia Commons, Microsoft; public domain designation on Commons.

Quick answer: completed age in TypeScript

For a conventional Gregorian-calendar age calculator, calculate the difference between the as-of year and birth year, then subtract one if the birthday in the as-of year has not happened yet.

function calculateAge(birthDate: Date, asOfDate: Date): number {
  if (birthDate.getTime() > asOfDate.getTime()) {
    throw new Error("Birth date cannot be after the calculation date.");
  }

  let age = asOfDate.getFullYear() - birthDate.getFullYear();

  const birthdayThisYear = new Date(
    asOfDate.getFullYear(),
    birthDate.getMonth(),
    birthDate.getDate()
  );

  if (birthdayThisYear.getTime() > asOfDate.getTime()) {
    age--;
  }

  return age;
}

const birthDate = new Date(1995, 8, 21);
const today = new Date();

console.log(calculateAge(birthDate, today));

The important part is the birthday comparison. A plain year subtraction can be one year too high when the birthday is still ahead in the current year.

Why TypeScript does not make date arithmetic automatic

TypeScript adds compile-time types to JavaScript, but its Date behavior comes from the JavaScript runtime. That means typing a variable as Date does not change how calendar arithmetic works. You still need an explicit birthday rule and careful handling of parsing.

ApproachProblemBetter pattern
Year difference onlyCan count an upcoming birthdayCompare this year’s birthday with the as-of date
Milliseconds divided by 365Leap years change the number of days in a yearUse calendar birthday logic for completed age
Ambiguous date stringsParsing can produce unexpected date valuesUse a controlled input format
Current date inside every functionHarder to testPass an explicit as-of date

Use an explicit as-of date

An age function is easier to reuse when it receives both dates. You can calculate today’s age, age at a historical event, or age on an eligibility cutoff without changing the calculation itself.

const birthDate = new Date(1988, 3, 15);
const asOfDate = new Date(2026, 8, 21);

const age = calculateAge(birthDate, asOfDate);

console.log("Age on " + asOfDate.toDateString() + ": " + age);

Parse a date input safely

For a browser form, an <input type="date"> supplies a value in YYYY-MM-DD form. It is useful to parse the components explicitly instead of relying on locale-dependent string interpretation.

function parseDateInput(value: string): Date {
  const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);

  if (!match) {
    throw new Error("Enter a date in YYYY-MM-DD format.");
  }

  const year = Number(match[1]);
  const month = Number(match[2]);
  const day = Number(match[3]);

  const date = new Date(year, month - 1, day);

  if (
    date.getFullYear() !== year ||
    date.getMonth() !== month - 1 ||
    date.getDate() !== day
  ) {
    throw new Error("Enter a valid calendar date.");
  }

  return date;
}

This validation also catches impossible dates such as April 31. The MDN documentation for date inputs explains that the control’s normalized value is a date string in yyyy-mm-dd form. MDN: input type=date

Build a browser age calculator in TypeScript

Here is a compact browser-oriented implementation that validates the input, rejects a future DOB, and displays the completed age.

const dobInput = document.querySelector<HTMLInputElement>("#dob");
const result = document.querySelector<HTMLElement>("#result");

if (!dobInput || !result) {
  throw new Error("Required elements are missing.");
}

dobInput.addEventListener("change", () => {
  try {
    const birthDate = parseDateInput(dobInput.value);
    const today = new Date();

    if (birthDate.getTime() > today.getTime()) {
      result.textContent = "Birth date cannot be in the future.";
      return;
    }

    const age = calculateAge(birthDate, today);
    result.textContent = "Completed age: " + age + " years";
  } catch (error) {
    result.textContent =
      error instanceof Error ? error.message : "Invalid date.";
  }
});

Calculate years, months, and days

If the calculator needs a result such as “31 years, 4 months, 6 days,” calculate completed years first. Then move a cursor forward by whole calendar months until another month would pass the as-of date. The remaining difference is the day component.

type AgeYmd = {
  years: number;
  months: number;
  days: number;
};

function calculateAgeYmd(
  birthDate: Date,
  asOfDate: Date
): AgeYmd {
  if (birthDate.getTime() > asOfDate.getTime()) {
    throw new Error("Birth date cannot be after the calculation date.");
  }

  let years = asOfDate.getFullYear() - birthDate.getFullYear();

  const anniversary = new Date(
    asOfDate.getFullYear(),
    birthDate.getMonth(),
    birthDate.getDate()
  );

  if (anniversary.getTime() > asOfDate.getTime()) {
    years--;
  }

  const cursor = new Date(
    birthDate.getFullYear() + years,
    birthDate.getMonth(),
    birthDate.getDate()
  );

  let months = 0;

  while (true) {
    const next = new Date(
      cursor.getFullYear(),
      cursor.getMonth() + 1,
      cursor.getDate()
    );

    if (next.getTime() > asOfDate.getTime()) {
      break;
    }

    cursor.setMonth(cursor.getMonth() + 1);
    months++;
  }

  const msPerDay = 24 * 60 * 60 * 1000;
  const days = Math.floor(
    (asOfDate.getTime() - cursor.getTime()) / msPerDay
  );

  return { years, months, days };
}

For production applications, test month-end and leap-day cases carefully because JavaScript Date normalizes out-of-range components. For applications requiring more sophisticated date-only semantics, the newer Temporal API is designed around separate calendar-date and date-time concepts, although MDN currently marks Temporal.PlainDate as having limited availability in browsers. MDN: Temporal.PlainDate

Calculate total elapsed days

Total elapsed days answer a different question from completed calendar age. If you need the number of 24-hour periods represented by two normalized dates, calculate their timestamp difference and divide by the number of milliseconds in a day. Be explicit about whether the application cares about local calendar dates or exact instants in time.

function totalElapsedDays(
  birthDate: Date,
  asOfDate: Date
): number {
  if (birthDate.getTime() > asOfDate.getTime()) {
    throw new Error("Birth date cannot be after the calculation date.");
  }

  const msPerDay = 24 * 60 * 60 * 1000;

  return Math.floor(
    (asOfDate.getTime() - birthDate.getTime()) / msPerDay
  );
}

Leap years and February 29

February 29 is the case that exposes many shortcuts. A person born on February 29 has a real birthday on leap years, but a non-leap year has no February 29. Your application should document whether its business rule treats the birthday as February 28, March 1, or follows another policy.

ScenarioRecommended implementation decision
Normal birthdayCompare month and day against the as-of date
Feb 29 DOB in leap yearUse the actual Feb 29 anniversary
Feb 29 DOB in non-leap yearApply the application’s documented business rule
Legal or eligibility ageFollow the governing rule rather than inventing a generic convention

Testing a TypeScript age calculator

Use fixed as-of dates in automated tests. The most useful cases sit immediately before, on, and immediately after the birthday.

Test caseExpected result
DOB equals as-of date0 years
One day before birthdayPrevious completed age
Birthday itselfNew completed age
Future DOBValidation error
Invalid calendar dateValidation error
Feb 29 DOBMatches the documented policy
Month-end datesNo unexpected month rollover

Common TypeScript date mistakes

  • Using year subtraction alone: this can overstate completed age by one.
  • Dividing milliseconds by 365 days: leap years and calendar boundaries make this unsuitable for completed calendar age.
  • Trusting arbitrary date strings: use a controlled format such as the value from a date input.
  • Ignoring future DOBs: reject dates after the as-of date unless your application has a specific reason to allow them.
  • Mixing local calendar dates with UTC instants: decide which concept the application actually needs before doing arithmetic.
  • Leaving Feb 29 undefined: document the rule when the distinction matters.

Date versus Temporal.PlainDate

RequirementPossible approachConsideration
Broad browser compatibilityJavaScript Date with careful logicBe explicit about parsing and calendar semantics
Typed application codeTypeScript types around the date functionsTypes improve interfaces but do not change runtime Date behavior
Date-only calendar modelTemporal.PlainDate where supported or appropriately polyfilledMDN currently lists limited browser availability
Exact time-zone instantA time-zone-aware date-time representationDifferent problem from a birthday-only calculation

Frequently asked questions

How do I calculate age from DOB in TypeScript?

Subtract the birth year from the as-of year, then subtract one when the birthday in that year is still in the future. Pass an explicit as-of date when you need a deterministic or historical calculation.

Can TypeScript calculate age without JavaScript Date?

TypeScript compiles to JavaScript, so a TypeScript application still needs a runtime date representation. You can wrap a library or the Temporal API in typed functions, but the underlying date semantics still need to be defined.

What is the safest way to read a DOB from an HTML form?

For a standard date input, validate the YYYY-MM-DD value and construct the calendar date from its numeric year, month, and day components. Do not assume that every arbitrary date string has identical parsing behavior across environments.

Should I use Temporal.PlainDate for an age calculator?

Temporal.PlainDate models a calendar date without a time or time zone, which matches the conceptual nature of a birthday. However, MDN currently marks Temporal as having limited availability, so browser support and any required polyfill should be considered before using it directly in a public web application.

Related age calculator tutorials

For other implementation stacks, see our age calculator in JavaScript, age calculator in React, age calculator in C#, and age calculator in Java tutorials.

Sources

Bottom line: A reliable age calculator in TypeScript needs calendar-aware birthday logic, controlled DOB parsing, future-date validation, and explicit handling for leap-day cases. TypeScript’s types make the interfaces clearer, while the date implementation still determines the actual calendar behavior.