How do I add a month to a Chrono NaiveDate?

4.4k Views Asked by At

I am trying to create a date picker and I need to navigate between months

let current = NaiveDate::parse_from_str("2020-10-15", "%Y-%m-%d").unwrap();

How can I generate the date 2020-11-15?

4

There are 4 best solutions below

0
On BEST ANSWER

Here is my solution:

use chrono::{ NaiveDate, Duration, Datelike};

fn get_days_from_month(year: i32, month: u32) -> u32 {
    NaiveDate::from_ymd(
        match month {
            12 => year + 1,
            _ => year,
        },
        match month {
            12 => 1,
            _ => month + 1,
        },
        1,
    )
    .signed_duration_since(NaiveDate::from_ymd(year, month, 1))
    .num_days() as u32
}

fn add_months(date: NaiveDate, num_months: u32) -> NaiveDate {
    let mut month = date.month() + num_months;
    let year = date.year() + (month / 12) as i32;
    month = month % 12;
    let mut day = date.day();
    let max_days = get_days_from_month(year, month);
    day = if day > max_days { max_days } else { day };
    NaiveDate::from_ymd(year, month, day)
}

fn main() {
    let date = NaiveDate::parse_from_str("2020-10-31", "%Y-%m-%d").unwrap();
    let new_date = add_months(date, 4);
    println!("{:?}", new_date);
}
0
On

Try chronoutil crate. There are chronoutil::delta::shift_months function for any Datelike.

    let current = NaiveDate::parse_from_str("2020-10-15", "%Y-%m-%d").unwrap();
    let next = shift_months(current, 1);

    assert_eq!(NaiveDate::parse_from_str("2020-11-15", "%Y-%m-%d").unwrap(), next);

Note:

Ambiguous month-ends are shifted backwards as necessary.

So for 2020-1-30 it gives 2020-2-29 and 2021-1-30 becomes 2021-2-28.

    let current = NaiveDate::parse_from_str("2021-1-30", "%Y-%m-%d").unwrap();
    let next = shift_months(current, 1);

    assert_eq!(NaiveDate::parse_from_str("2021-2-28", "%Y-%m-%d").unwrap(), next);
0
On

You are actually asking for two different things. To go to the next month is not the same as adding a month to a date, because with the latter you also have to consider the validity of the day of the month. If you add a month to the 29th of January for example, you'll usually end up in March instead of February because normally there is no 29th of February, except of course in leap years.

Dealing with just years and months is much simpler, because the number of months in a year is the same for every year. So to answer the underlying question of how to navigate between months in a date picker, here you go:

fn next_month(year: i32, month: u32) -> (i32, u32) {
  assert!(month >= 1 && month <= 12);

  if month == 12 {
    (year + 1, 1)
  } else {
    (year, month + 1)
  }
}

fn prev_month(year: i32, month: u32) -> (i32, u32) {
  assert!(month >= 1 && month <= 12);

  if month == 1 {
    (year - 1, 12)
  } else {
    (year, month - 1)
  }
}
0
On

NaiveDate now has a method for this checked_add_months

assert_eq!(
    NaiveDate::from_ymd(2020, 10, 15).checked_add_months(chrono::Months::new(1)),
    Some(NaiveDate::from_ymd(2020, 11, 15))
);
assert_eq!(
    NaiveDate::from_ymd(2020, 10, 15).checked_add_months(chrono::Months::new(3)),
    Some(NaiveDate::from_ymd(2021, 1, 15))
);