Rust Date Recipes
A date recipe is a ready-made pattern for a common date job. Each recipe below uses fixed dates, so every result is stable.
Sort and pick the ends
Dates sort like numbers. The first is the earliest:
let mut days = vec![NaiveDate::from_ymd_opt(2026, 10, 22).unwrap(), NaiveDate::from_ymd_opt(2026, 9, 30).unwrap(), NaiveDate::from_ymd_opt(2026, 11, 1).unwrap()];
days.sort();
println!("sorted = {:?}", days);
println!("earliest = {} latest = {}", days.first().unwrap(), days.last().unwrap());Days between, both ways
Subtract dates to count the gap. The order sets the sign:
let start = NaiveDate::from_ymd_opt(2026, 10, 22).unwrap();
let end = NaiveDate::from_ymd_opt(2026, 11, 1).unwrap();
println!("forward = {}", (end - start).num_days());
println!("backward = {}", (start - end).num_days());
println!("inclusive = {}", (end - start).num_days() + 1);Work out an age
Start with the year gap. Take one year back if the birthday is still ahead:
fn age_on(born: NaiveDate, day: NaiveDate) -> i32 {
let mut age = day.year() - born.year();
if (day.month(), day.day()) < (born.month(), born.day()) {
age -= 1;
}
age
}
let born = NaiveDate::from_ymd_opt(2000, 10, 23).unwrap();
println!("day before = {}", age_on(born, NaiveDate::from_ymd_opt(2026, 10, 22).unwrap()));
println!("birthday = {}", age_on(born, NaiveDate::from_ymd_opt(2026, 10, 23).unwrap()));A leap-day birthday
This recipe treats 1 March as the reached day in a plain year. That choice is a product rule, not a chrono rule:
let leap_born = NaiveDate::from_ymd_opt(2000, 2, 29).unwrap();
println!("28 Feb = {}", age_on(leap_born, NaiveDate::from_ymd_opt(2026, 2, 28).unwrap()));
println!("1 Mar = {}", age_on(leap_born, NaiveDate::from_ymd_opt(2026, 3, 1).unwrap()));The edges of one day
A day can end at its last nanosecond. A range is safer when it ends at the next midnight:
let day = NaiveDate::from_ymd_opt(2026, 10, 22).unwrap();
println!("start = {}", day.and_hms_opt(0, 0, 0).unwrap());
println!("last nano = {}", day.and_hms_nano_opt(23, 59, 59, 999_999_999).unwrap());
println!("next start = {}", day.checked_add_days(Days::new(1)).unwrap().and_hms_opt(0, 0, 0).unwrap());Find the next Monday
Step one day at a time. Stop when the weekday matches:
let mut next = NaiveDate::from_ymd_opt(2026, 10, 22).unwrap();
while next.weekday() != Weekday::Mon {
next = next.checked_add_days(Days::new(1)).unwrap();
}
println!("next Monday = {} {}", next, next.weekday());Example
// Cargo.toml: chrono = "0.4" (verified with chrono 0.4.45)
use chrono::{Datelike, Days, NaiveDate};
fn age_on(born: NaiveDate, day: NaiveDate) -> i32 {
let mut age = day.year() - born.year();
if (day.month(), day.day()) < (born.month(), born.day()) {
age -= 1;
}
age
}
fn main() {
let born = NaiveDate::from_ymd_opt(2000, 10, 23).unwrap();
let mut events = vec![
NaiveDate::from_ymd_opt(2026, 11, 1).unwrap(),
NaiveDate::from_ymd_opt(2026, 10, 22).unwrap(),
NaiveDate::from_ymd_opt(2026, 10, 30).unwrap(),
];
events.sort();
let first = events[0];
let last = events[events.len() - 1];
println!("first event = {} {}", first, first.weekday());
println!("last event = {} {}", last, last.weekday());
println!("span = {} days ({} including both ends)", (last - first).num_days(), (last - first).num_days() + 1);
println!("age on first event = {}", age_on(born, first));
println!("first day starts = {}", first.and_hms_opt(0, 0, 0).unwrap());
println!("range ends at next midnight = {}", last.checked_add_days(Days::new(1)).unwrap().and_hms_opt(0, 0, 0).unwrap());
}Key points
- Sort dates directly.
- Their order is calendar order.
- A signed day count shows direction.
- Swap the dates and the sign flips.
- Add one for an inclusive count.
- The count then includes both end dates.
- Age is not just a year gap.
- The birthday must have happened in the later year.
- Use a half-open day range.
- It starts at one midnight and ends at the next midnight.
- A date recipe is a reusable pattern that solves one everyday date job.
In this example, three event dates are sorted. The first is 22 October. The last is 1 November. The span is 10 days, or 11 dates with both ends counted. The guest is still 25 on the first date. The birthday falls one day later.
Note
Date-only recipes dodge clock changes.
Work out the local date first when โtodayโ means a place.
Try it Yourself
Edit the code, then press Run.
Running is simulated ยท Rust Playground
Exercise
A stay runs from 22 October to 1 November. How many dates does it cover, counting both ends?