Age Calculator in Rust is a useful example of calendar-aware programming because a person’s age is not simply the number of hours between two timestamps. For completed age, the program needs to compare a date of birth with a reference date and account for whether the birthday has occurred. In Rust, the chrono crate provides NaiveDate, date parsing, calendar arithmetic, elapsed-day calculations and timezone-aware date-time types. The current docs.rs documentation lists Chrono 0.4.45 and describes it as a date-and-time library designed for operations in the proleptic Gregorian calendar. Read the Chrono documentation.

Quick Answer: Simple Rust Age Calculator
For a normal completed-age calculation, parse the DOB into a NaiveDate, obtain the reference date, and calculate the number of whole years. Chrono’s NaiveDate::years_since() method is designed to return whole years from one date to another. That is a better starting point for calendar age than dividing an elapsed duration by an assumed 365-day year.
use chrono::{Local, NaiveDate};
fn calculate_age(dob: NaiveDate, as_of: NaiveDate) -> Result<u32, &'static str> {
if dob > as_of {
return Err("date of birth cannot be after the reference date");
}
Ok(as_of.years_since(dob).unwrap())
}
fn main() {
let dob = NaiveDate::parse_from_str("2000-06-15", "%Y-%m-%d").unwrap();
let today = Local::now().date_naive();
let age = calculate_age(dob, today).unwrap();
println!("Age: {age}");
}The important part is the definition of age: completed calendar years. If you need age on a historical cutoff date, pass that date explicitly instead of using the system clock.
Which Rust Date Types and Tools Should You Use?
For a date-of-birth calculator, NaiveDate is often the clearest type because it represents an ISO 8601 calendar date without a timezone. Chrono also provides timezone-aware DateTime values when an exact instant or local clock time matters. The library documentation recommends choosing the date type that matches the problem rather than treating every date as a timestamp.
| Rust/Chrono tool | Age-calculator purpose |
|---|---|
NaiveDate | Stores a date of birth or date-only cutoff |
NaiveDate::parse_from_str() | Parses a DOB such as 2000-06-15 |
years_since() | Calculates whole calendar years |
checked_add_months() | Builds calendar-month calculations while returning Option |
signed_duration_since() | Measures elapsed days between two dates |
Local::now() | Gets the current local date/time when the local calendar date is required |
Chrono’s documentation also notes that operations that may produce an invalid or ambiguous date/time can return Option or another mapping type. For production code, prefer the checked APIs when invalid dates or calendar boundaries need explicit handling.
Set Up a Rust Project for Age Calculation
Create a normal Cargo project and add Chrono as a dependency. The Rust documentation describes Cargo as Rust’s package manager and build tool. The current Rust Book uses Rust 1.90.0 or later with the 2024 edition for its examples, while the exact compiler version in your own project should be controlled by your project’s toolchain and lockfile.
cargo new age_calculator
cd age_calculatorThen add Chrono to Cargo.toml. If you want to follow the current Chrono documentation used for this article, the docs.rs page currently lists version 0.4.45.
[dependencies]
chrono = "0.4.45"Pinning or locking a dependency through Cargo’s normal workflow helps make builds reproducible. Check the current crate documentation before starting a new project because library versions can change after this article is published.
1. Calculate Current Age from Date of Birth
A current-age calculator has two inputs: the DOB and today’s date. For a date-only problem, Local::now().date_naive() gives the local calendar date. If the application should always use a fixed timezone, define that rule instead of depending on the server’s location.
use chrono::{Local, NaiveDate};
fn main() {
let dob = NaiveDate::from_ymd_opt(1998, 9, 22).unwrap();
let today = Local::now().date_naive();
let age = today.years_since(dob).unwrap();
println!("DOB: {dob}");
println!("Today: {today}");
println!("Completed age: {age}");
}This is concise because Chrono performs the whole-year calendar calculation for you. The method returns None when the base date is after the date being examined, so validate the input order before unwrapping the result.
2. Calculate Age on a Specific Cutoff Date
Many eligibility calculations are based on a notification cutoff, application closing date or another historical reference date. In those cases, using Local::now() would give the wrong answer once the cutoff has passed. Make the reference date an explicit function argument.
use chrono::NaiveDate;
fn calculate_age(dob: NaiveDate, as_of: NaiveDate) -> Result<u32, &'static str> {
if dob > as_of {
return Err("DOB cannot be after the reference date");
}
Ok(as_of.years_since(dob).unwrap())
}
fn main() {
let dob = NaiveDate::from_ymd_opt(2000, 6, 15).unwrap();
let cutoff = NaiveDate::from_ymd_opt(2026, 9, 21).unwrap();
println!("{}", calculate_age(dob, cutoff).unwrap());
}With this design, the same function can answer questions such as “How old was this person on the application deadline?” without changing the calculation logic.
3. Parse a DOB from User Input
Real applications usually receive the DOB as text. Chrono’s NaiveDate::parse_from_str() accepts a string and a formatting pattern. For the common ISO-style input YYYY-MM-DD, the layout is %Y-%m-%d.
use chrono::NaiveDate;
fn parse_dob(input: &str) -> Result<NaiveDate, String> {
NaiveDate::parse_from_str(input, "%Y-%m-%d")
.map_err(|err| format!("invalid date: {err}"))
}
fn main() {
match parse_dob("2000-06-15") {
Ok(dob) => println!("DOB: {dob}"),
Err(err) => eprintln!("{err}"),
}
}Do not treat a parsing error as an age of zero. Return the error to the user interface or calling service so invalid input can be corrected. This also keeps date parsing separate from the actual age-calculation rule.

4. Calculate Age in Years, Months and Days
Sometimes “age” needs more detail than completed years. A common display is something like 26 years, 3 months and 7 days. There is no single universal convention for every month-end and leap-day case, so your application should document its rule. Chrono’s calendar-month operations are useful because they understand variable month lengths.
use chrono::{Months, NaiveDate};
fn age_ymd(dob: NaiveDate, as_of: NaiveDate) -> Result<(u32, u32, i64), &'static str> {
if dob > as_of {
return Err("DOB cannot be after the reference date");
}
let years = as_of.years_since(dob).unwrap();
let mut cursor = dob
.checked_add_months(Months::new(years * 12))
.ok_or("date is out of range")?;
let mut months = 0;
while let Some(next) = cursor.checked_add_months(Months::new(1)) {
if next > as_of {
break;
}
cursor = next;
months += 1;
}
let days = (as_of - cursor).num_days();
Ok((years, months, days))
}Chrono documents that adding months to a date clamps to the last valid day when the original day does not exist in the destination month. That behavior is important to understand before using month-by-month arithmetic for an age display. Test month-end birthdays and decide whether the library’s clamping behavior matches your application’s definition.
5. Calculate Total Days Between Two Dates
If the requirement is total elapsed calendar days rather than completed age, subtract the two NaiveDate values. Chrono returns a TimeDelta, and its num_days() method gives the whole-day difference.
use chrono::NaiveDate;
fn main() {
let dob = NaiveDate::from_ymd_opt(2000, 6, 15).unwrap();
let as_of = NaiveDate::from_ymd_opt(2026, 9, 21).unwrap();
let days = (as_of - dob).num_days();
println!("Elapsed days: {days}");
}This is an elapsed-day result, not a replacement for calendar age. A calendar year may contain 365 or 366 days, and a calendar month is not a fixed number of days.
6. Calculate Complete Weeks
For complete elapsed weeks, divide the total day count by seven using integer division.
let total_days = (as_of - dob).num_days();
let complete_weeks = total_days / 7;
println!("Complete weeks: {complete_weeks}");Use this when the requirement explicitly asks for elapsed weeks. If the requirement asks for the ISO week number of a particular date, that is a different calendar concept.
7. Handle Leap Years and February 29
Leap years are one of the easiest ways to expose a weak age-calculation algorithm. Chrono’s date types use the proleptic Gregorian calendar and expose a leap_year() method. More importantly, NaiveDate refuses impossible dates when you use the checked constructors.
use chrono::NaiveDate;
let leap_day = NaiveDate::from_ymd_opt(2020, 2, 29).unwrap();
assert!(leap_day.leap_year());
assert!(NaiveDate::from_ymd_opt(2021, 2, 29).is_none());A 29 February birthday needs a business rule when the reference year is not a leap year. Some applications treat the birthday as 28 February, others use 1 March, and official rules can vary by context. The programming library cannot decide that policy for you. Encode the chosen rule and test it explicitly.
8. Validate Future and Invalid DOB Values
There are two different validation questions. First, is the text a real calendar date? Chrono handles that during parsing. Second, is the date acceptable for this age calculation? A future DOB should normally be rejected when calculating age as of a current or past reference date.
use chrono::NaiveDate;
fn validate_dob(dob: NaiveDate, as_of: NaiveDate) -> Result<(), &'static str> {
if dob > as_of {
return Err("date of birth cannot be in the future");
}
Ok(())
}Keep this business validation close to the application boundary. A reusable calculation function can then receive valid dates and focus on returning the correct age.
9. When Time Zones Matter
If your application only needs a date such as 22 September 2026, NaiveDate avoids unnecessary timezone complexity. If the question is “what is today’s date for this user?” the timezone becomes important because a server in UTC and a user in another region can be on different calendar dates around midnight.
use chrono::Local;
let local_date = Local::now().date_naive();
println!("Local calendar date: {local_date}");For an application with a known user location, use the appropriate timezone-aware Chrono type and convert to a local date deliberately. Chrono’s documentation notes that the Local timezone follows the operating system’s current timezone and that full timezone data can be supplied through companion crates such as Chrono-TZ.
10. Format the Result for Users
Dates are often stored or exchanged in an ISO-style form but displayed in a friendlier format. Chrono’s format() method uses strftime-style formatting.
let dob = NaiveDate::from_ymd_opt(2000, 6, 15).unwrap();
println!("{}", dob.format("%d %B %Y"));Keep the machine-readable input format separate from the display format. That makes validation easier and avoids parsing ambiguities such as whether 06/07/2000 means 6 July or 7 June.
11. Build a Reusable Rust Age Calculator
A good reusable function should not secretly call the current clock. Pass the reference date into the function so a caller can use today, a historical date or a future cutoff. This also makes unit tests deterministic.
use chrono::NaiveDate;
pub fn calculate_age(
dob: NaiveDate,
as_of: NaiveDate,
) -> Result<u32, &'static str> {
if dob > as_of {
return Err("DOB cannot be after reference date");
}
Ok(as_of.years_since(dob).unwrap())
}
fn main() {
let dob = NaiveDate::from_ymd_opt(2000, 6, 15).unwrap();
let cutoff = NaiveDate::from_ymd_opt(2026, 9, 21).unwrap();
match calculate_age(dob, cutoff) {
Ok(age) => println!("Completed age: {age}"),
Err(err) => eprintln!("Error: {err}"),
}
}This separation is useful in command-line programs, web APIs, database services and eligibility tools. The input layer parses dates, the calculation layer computes age, and the presentation layer decides how to display the result.
Worked Age Examples
| Date of birth | Reference date | Completed age |
|---|---|---|
| 15-Jun-2000 | 21-Sep-2026 | 26 |
| 15-Jun-2000 | 01-Jun-2026 | 25 |
| 22-Sep-1998 | 22-Sep-2026 | 28 |
| 22-Sep-1998 | 21-Sep-2026 | 27 |
| 29-Feb-2000 | 28-Feb-2026 | Depends on the application’s leap-day rule |
The boundary rows are important. A person born on 15 June 2000 is 25 on 1 June 2026 but 26 on 21 September 2026. A person born on 22 September 1998 turns 28 on 22 September 2026, not the day before.
How to Test a Rust Age Calculator
Use fixed dates in unit tests. Testing against the real current date makes the test change its expected result as time passes.
#[cfg(test)]
mod tests {
use super::*;
use chrono::NaiveDate;
fn date(y: i32, m: u32, d: u32) -> NaiveDate {
NaiveDate::from_ymd_opt(y, m, d).unwrap()
}
#[test]
fn birthday_has_arrived() {
assert_eq!(calculate_age(date(2000, 6, 15), date(2026, 9, 21)), Ok(26));
}
#[test]
fn birthday_has_not_arrived() {
assert_eq!(calculate_age(date(2000, 6, 15), date(2026, 6, 1)), Ok(25));
}
#[test]
fn birthday_is_today() {
assert_eq!(calculate_age(date(2000, 6, 15), date(2026, 6, 15)), Ok(26));
}
#[test]
fn future_dob_is_rejected() {
assert!(calculate_age(date(2030, 1, 1), date(2026, 9, 21)).is_err());
}
}Add separate tests for 29 February, month ends, invalid input strings, date boundaries and timezone behavior when those cases are part of your product requirements.
Common Rust Age-Calculation Mistakes
- Subtracting only the years. A raw year difference can be one too high before the birthday.
- Dividing days by 365. This ignores leap years and does not produce reliable calendar age.
- Confusing elapsed time with age.
TimeDeltais useful for durations, but completed age is calendar-based. - Using an implicit current date for a cutoff calculation. Eligibility checks should pass the required reference date explicitly.
- Ignoring invalid input. Parsing errors and future DOBs should be handled instead of converted into a plausible-looking number.
- Assuming one February 29 rule. Decide and document the rule required by your application.
- Using timezone-aware timestamps for every date-only problem. A date-only value can be simpler and safer when time of day is irrelevant.
Rust vs Other Age-Calculator Implementations
| Implementation | Main date approach | Useful for |
|---|---|---|
| Rust | Chrono NaiveDate and calendar methods | Rust applications and services |
| Go | time.Time and birthday-aware logic | Go services and command-line tools |
| Java | LocalDate and Period | Java applications and backend services |
| C++ | Chrono calendar facilities | Native applications |
| Swift | Foundation Calendar | Apple-platform applications |
| Python | datetime and calendar logic | Scripts and web applications |
| LibreOffice Calc | Date formulas such as DATEDIF | Spreadsheet calculations |
For related tutorials, see our guides to Age Calculator in Go, Age Calculator in Java, Age Calculator in C++, Age Calculator in Swift, Age Calculator in Python and Age Calculator in LibreOffice Calc.
FAQs
How do I calculate age in Rust?
Parse the DOB and reference date as NaiveDate values, reject a future DOB, and use years_since() for completed calendar years. If you need years, months and days, build that result with explicit calendar-month and day logic.
Does Rust have a built-in age calculator?
Rust itself does not provide a dedicated age-calculator function. A date library such as Chrono supplies the calendar operations needed to build one.
What is the best Rust type for a date of birth?
For a date-only DOB, chrono::NaiveDate is a natural choice because it represents a calendar date without a timezone. Use a timezone-aware DateTime when the exact instant or local clock context matters.
How do I calculate total days between DOB and today?
Subtract one NaiveDate from the other and call num_days() on the resulting TimeDelta. That gives elapsed calendar days, which is different from completed calendar years.
How should February 29 be handled?
Define the rule required by your application and test it. A leap-day birthday can fall in a non-leap reference year, and the appropriate treatment can depend on the business or legal context.
Can I calculate age on a past cutoff date?
Yes. Pass the cutoff date as the reference date. This is preferable to calling the current clock when the calculation is tied to a historical application, exam or eligibility date.
Useful Rust and Chrono References
- The Rust Programming Language — official Rust Book.
- The Cargo Book — official Cargo documentation.
- Chrono documentation — date, time, parsing and calendar operations.
- Chrono NaiveDate documentation — date parsing, whole-year calculations and calendar arithmetic.
Final Takeaway
A reliable age calculator in Rust should treat age as a calendar calculation. For completed years, Chrono’s NaiveDate::years_since() provides a direct way to count whole years. For more detailed output, combine calendar-month operations with day differences and document how month-end and leap-day cases are handled.
For production use, keep the reference date explicit, validate future DOBs, use a date-only type when time zones are irrelevant, and add tests around birthdays and leap years. These small choices prevent the most common off-by-one errors and make the calculator easier to reuse in Rust applications.
Technical note: this article describes programming techniques. It does not establish a universal legal definition of age. When an age result is used for an official eligibility decision, follow the exact rule and cutoff date specified by the relevant authority.
