Rust Date Arithmetic
Date arithmetic adds or subtracts amounts of time from a date. One month is not a fixed count of days.
Add days the checked way
`checked_add_days` takes a `Days` value. It crosses the month end for you:
let jan30 = NaiveDate::from_ymd_opt(2026, 1, 30).unwrap();
println!("plus 2 days = {:?}", jan30.checked_add_days(Days::new(2)));Weeks are seven-day steps
A `TimeDelta` can hold whole weeks. Checked subtraction walks the other way:
let jan30 = NaiveDate::from_ymd_opt(2026, 1, 30).unwrap();
println!("plus 2 weeks = {:?}", jan30.checked_add_signed(TimeDelta::weeks(2)));
println!("minus 2 weeks = {:?}", jan30.checked_sub_signed(TimeDelta::weeks(2)));One month can clamp the day
February has no 31st day. The month step lands on its last day:
let jan31 = NaiveDate::from_ymd_opt(2026, 1, 31).unwrap();
println!("one month later = {:?}", jan31.checked_add_months(Months::new(1)));Leap years change the clamp
The same step lands one day later in a leap year. Twelve months keep the day here:
let leap = NaiveDate::from_ymd_opt(2024, 1, 31).unwrap();
println!("leap one month = {:?}", leap.checked_add_months(Months::new(1)));
let jan31 = NaiveDate::from_ymd_opt(2026, 1, 31).unwrap();
println!("twelve months = {:?}", jan31.checked_add_months(Months::new(12)));Whole days can hide hours
This gap is 47 hours. It is one whole day plus 23 hours:
let a = NaiveDate::from_ymd_opt(2026, 1, 1).unwrap().and_hms_opt(12, 0, 0).unwrap();
let b = NaiveDate::from_ymd_opt(2026, 1, 3).unwrap().and_hms_opt(11, 0, 0).unwrap();
let gap = b.signed_duration_since(a);
println!("gap hours = {}", gap.num_hours());
println!("whole days = {}", gap.num_days());
println!("reverse days = {}", a.signed_duration_since(b).num_days());The edge of the calendar
Checked steps stop at the edge. They return `None`:
println!("past the end = {:?}", NaiveDate::MAX.checked_add_days(Days::new(1)));Example
// Cargo.toml: chrono = "0.4" (verified with chrono 0.4.45)
use chrono::{Days, Months, NaiveDate};
fn main() {
let start = NaiveDate::from_ymd_opt(2026, 1, 31).unwrap();
let one_month = start.checked_add_months(Months::new(1)).unwrap();
let three_months = start.checked_add_months(Months::new(3)).unwrap();
let extended = three_months.checked_add_days(Days::new(10)).unwrap();
println!("starts {}", start);
println!("one month later {}", one_month);
println!("three months later {}", three_months);
println!("with 10 extra days {}", extended);
println!("whole plan = {} days", extended.signed_duration_since(start).num_days());
}Key points
- Use `Days` for whole calendar days.
- Use `Months` for calendar months.
- Checked methods return an `Option`.
- `None` means the result is out of range.
- Month steps clamp the day.
- 31 January plus one month lands on the last day of February.
- `TimeDelta` holds a signed length of time.
- It has no month or year unit.
- Use `signed_duration_since` for a gap.
- The sign shows the direction of the gap.
In this example, the plan starts on 31 January 2026. One month clamps to 28 February. Three months clamp to 30 April. Ten extra days then reach 10 May. The full span is 99 days.
Note
`std::time::Duration` cannot be negative.
Chrono `TimeDelta` can be negative.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
What is 31 January 2026 plus `Months::new(1)`?