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.

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.
| Approach | Problem | Better pattern |
|---|---|---|
| Year difference only | Can count an upcoming birthday | Compare this year’s birthday with the as-of date |
| Milliseconds divided by 365 | Leap years change the number of days in a year | Use calendar birthday logic for completed age |
| Ambiguous date strings | Parsing can produce unexpected date values | Use a controlled input format |
| Current date inside every function | Harder to test | Pass 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.
| Scenario | Recommended implementation decision |
|---|---|
| Normal birthday | Compare month and day against the as-of date |
| Feb 29 DOB in leap year | Use the actual Feb 29 anniversary |
| Feb 29 DOB in non-leap year | Apply the application’s documented business rule |
| Legal or eligibility age | Follow 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 case | Expected result |
|---|---|
| DOB equals as-of date | 0 years |
| One day before birthday | Previous completed age |
| Birthday itself | New completed age |
| Future DOB | Validation error |
| Invalid calendar date | Validation error |
| Feb 29 DOB | Matches the documented policy |
| Month-end dates | No 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
| Requirement | Possible approach | Consideration |
|---|---|---|
| Broad browser compatibility | JavaScript Date with careful logic | Be explicit about parsing and calendar semantics |
| Typed application code | TypeScript types around the date functions | Types improve interfaces but do not change runtime Date behavior |
| Date-only calendar model | Temporal.PlainDate where supported or appropriately polyfilled | MDN currently lists limited browser availability |
| Exact time-zone instant | A time-zone-aware date-time representation | Different 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.