Building an age calculator in C# is straightforward once you treat age as a calendar calculation rather than a simple subtraction of years. This guide shows how to calculate completed age from a date of birth, calculate age on a fixed date, return years-months-days, validate future dates, and calculate elapsed days with modern .NET date types.

Quick answer: completed age in C#
For a date-only age calculator on .NET 6 and later, DateOnly is a natural fit because a birthday has a date but no time-of-day component. Microsoft documents DateOnly as a type for representing a specific date without a time, and specifically notes that it is useful for dates such as birth dates. Microsoft Learn: DateOnly and TimeOnly
using System;
static int CalculateAge(DateOnly birthDate, DateOnly asOfDate)
{
if (birthDate > asOfDate)
throw new ArgumentException("Birth date cannot be after the calculation date.");
int age = asOfDate.Year - birthDate.Year;
if (birthDate.AddYears(age) > asOfDate)
age--;
return age;
}
var birthDate = new DateOnly(1995, 9, 21);
var today = DateOnly.FromDateTime(DateTime.Today);
Console.WriteLine(CalculateAge(birthDate, today));
The key check is birthDate.AddYears(age) > asOfDate. It prevents the calculator from counting the current calendar year before the birthday has actually occurred.
Why subtracting the years is not enough
A tempting implementation is asOfDate.Year - birthDate.Year. That gives the right result only when the birthday has already occurred in the calculation year. For example, someone born on December 10, 2000 is not yet 26 on September 21, 2026; the raw year difference is 26, while the completed age is 25.
| Birth date | As-of date | Year difference | Completed age |
|---|---|---|---|
| 10 Dec 2000 | 21 Sep 2026 | 26 | 25 |
| 10 Sep 2000 | 21 Sep 2026 | 26 | 26 |
| 21 Sep 2000 | 21 Sep 2026 | 26 | 26 |
DateOnly versus DateTime in an age calculator
DateOnly was introduced with .NET 6 and represents a date without a time. Microsoft notes that this can avoid unnecessary time-zone and time-of-day concerns when the value is inherently a calendar date. It is therefore a good match for a birth date and an as-of date in a conventional age calculator. Microsoft Learn: choosing between DateTime and DateOnly
| Type | Use in an age calculator | Typical concern |
|---|---|---|
| DateOnly | Birth date and calendar as-of date | Requires .NET 6+ |
| DateTime | Older applications or date-time APIs | Time and time-zone semantics can be unnecessary |
| DateTimeOffset | When a real instant in time matters | Usually more detail than a birthday calculation needs |
Age on a fixed date
An age calculator should usually accept an explicit as-of date rather than always using the machine’s current date. That makes the function deterministic, easier to test, and useful for eligibility or historical calculations.
var birthDate = new DateOnly(1988, 4, 15);
var asOfDate = new DateOnly(2026, 9, 21);
int age = CalculateAge(birthDate, asOfDate);
Console.WriteLine($"Age on {asOfDate:dd MMM yyyy}: {age}");
Calculate years, months, and days
If your UI needs an exact calendar-style result such as “31 years, 4 months, 6 days,” calculate the completed years first, then advance from the anniversary by whole months, and finally count the remaining calendar days. DateOnly.DayNumber provides a convenient integer representation for the day difference. Microsoft documents DayNumber as the number of days since January 1, 0001 in the proleptic Gregorian calendar. Microsoft Learn: DateOnly
static (int Years, int Months, int Days) CalculateAgeYmd(
DateOnly birthDate,
DateOnly asOfDate)
{
if (birthDate > asOfDate)
throw new ArgumentException("Birth date cannot be after the calculation date.");
int years = asOfDate.Year - birthDate.Year;
DateOnly anniversary = birthDate.AddYears(years);
if (anniversary > asOfDate)
{
years--;
anniversary = birthDate.AddYears(years);
}
int months = 0;
DateOnly cursor = anniversary;
while (cursor.AddMonths(1) <= asOfDate)
{
cursor = cursor.AddMonths(1);
months++;
}
int days = asOfDate.DayNumber - cursor.DayNumber;
return (years, months, days);
}
var result = CalculateAgeYmd(
new DateOnly(1990, 5, 17),
new DateOnly(2026, 9, 21));
Console.WriteLine($"{result.Years} years, {result.Months} months, {result.Days} days");
This approach keeps the units calendar-aware: years are completed birthdays, months are completed calendar months after the last anniversary, and the final value is the remaining number of days.
Calculate total elapsed days
Sometimes “age” means the total number of days elapsed rather than completed years. With DateOnly, subtracting the two DayNumber values gives a direct day count.
static int TotalDays(DateOnly birthDate, DateOnly asOfDate)
{
if (birthDate > asOfDate)
throw new ArgumentException("Birth date cannot be after the calculation date.");
return asOfDate.DayNumber - birthDate.DayNumber;
}
Validate a date of birth
Never silently accept a future birth date. If the value comes from a web form, API, database, or user input, parse and validate it before running the calculation.
if (!DateOnly.TryParse(input, out DateOnly birthDate))
{
Console.WriteLine("Enter a valid date.");
return;
}
DateOnly asOfDate = DateOnly.FromDateTime(DateTime.Today);
if (birthDate > asOfDate)
{
Console.WriteLine("Birth date cannot be in the future.");
return;
}
Console.WriteLine(CalculateAge(birthDate, asOfDate));
A complete C# console example
using System;
static int CalculateAge(DateOnly birthDate, DateOnly asOfDate)
{
if (birthDate > asOfDate)
throw new ArgumentException("Birth date cannot be after the calculation date.");
int age = asOfDate.Year - birthDate.Year;
if (birthDate.AddYears(age) > asOfDate)
age--;
return age;
}
Console.Write("Enter date of birth (yyyy-MM-dd): ");
string? input = Console.ReadLine();
if (!DateOnly.TryParse(input, out DateOnly birthDate))
{
Console.WriteLine("Invalid date.");
return;
}
DateOnly today = DateOnly.FromDateTime(DateTime.Today);
if (birthDate > today)
{
Console.WriteLine("Birth date cannot be in the future.");
return;
}
int age = CalculateAge(birthDate, today);
Console.WriteLine($"Completed age: {age} years");
Console.WriteLine($"Total elapsed days: {today.DayNumber - birthDate.DayNumber}");
Leap years and February 29 birthdays
February 29 needs an explicit business rule when the target year is not a leap year. The sample above relies on .NET’s calendar behavior when AddYears produces a date whose day is invalid in the target year: the resulting day is adjusted to the last valid day of that month. Microsoft documents this adjustment behavior for calendar year addition. Microsoft Learn: Calendar.AddYears
| Case | What to decide |
|---|---|
| Feb 29 DOB, non-leap as-of year | Whether the birthday is treated as Feb 28 or another local/business rule |
| Feb 29 DOB, leap as-of year | The calendar date exists, so the normal anniversary comparison applies |
| Eligibility rule | Follow the specific policy if a law, employer, exam, or application defines a different cutoff rule |
Testing an age calculator in C#
Test dates around the birthday rather than testing only obvious mid-year examples. Fixed as-of dates make the tests repeatable.
| Test | Expected behavior |
|---|---|
| DOB equals as-of date | 0 years |
| One day before birthday | Previous completed age |
| Birthday itself | New completed age |
| DOB in the future | Validation error |
| Feb 29 DOB | Matches the application’s documented birthday policy |
| Very old valid DOB | No integer or calendar arithmetic shortcut should produce an incorrect result |
Common mistakes
- Subtracting years only: this can overstate age before the birthday.
- Dividing elapsed days by 365: leap years make that unsuitable for completed calendar age.
- Ignoring future dates: a future DOB should be rejected unless the application has a special reason to allow it.
- Mixing time zones into a date-only problem: use
DateOnlywhen the input is fundamentally a calendar date. - Leaving Feb 29 undefined: document the policy your application follows.
- Using today’s date inside every function: accepting an as-of date makes the calculation easier to test and reuse.
C# age calculator: DateOnly versus DateTime
| Requirement | Recommended approach |
|---|---|
| Birthday and calendar age | DateOnly on .NET 6+ |
| Legacy .NET Framework project | DateTime with the time portion normalized or ignored carefully |
| Exact instant across time zones | DateTimeOffset may be more appropriate |
| Age on a historical date | Pass an explicit DateOnly asOfDate |
Frequently asked questions
How do I calculate age from DOB in C#?
Subtract the birth year from the as-of year, then reduce the result by one when the birthday anniversary in that year is later than the as-of date. The DateOnly.AddYears approach shown above keeps that comparison calendar-aware.
Can I use DateTime instead of DateOnly?
Yes. DateTime is still useful for applications that target older frameworks or APIs that require it. For a pure birthday calculation, however, DateOnly communicates that the time of day is not part of the data.
How do I calculate age as of a particular date in C#?
Pass the desired date as the second argument to the age function instead of reading the current date inside the function. This lets the same calculation work for today’s age, a historical date, or a future eligibility cutoff.
Does C# handle leap years?
Yes. The .NET date types implement calendar rules, but your application still needs a clear policy for interpreting a February 29 birthday in a non-leap year when that distinction matters.
Related age calculator tutorials
If you are implementing the same calculation in another language or stack, see our guides to the age calculator in JavaScript, age calculator in Java, age calculator in Python, and age calculator in SQL.
Sources
- Microsoft Learn: How to use DateOnly and TimeOnly
- Microsoft Learn: Choose between DateTime, DateTimeOffset, TimeOnly, and DateOnly
- Microsoft Learn: DateOnly
- Microsoft Learn: Calendar.AddYears
Bottom line: For a modern .NET application, use DateOnly for birth dates, compare the birthday anniversary against the as-of date, validate future DOBs, and make any February 29 policy explicit. That produces an age calculator in C# that is easier to test and less prone to off-by-one errors.