Skip to main content

end_of_month/
lib.rs

1use chrono::{NaiveDate, Datelike, Duration, Local};
2
3/// Parses a date string in MM/DD/YYYY format and returns a NaiveDate.
4/// Returns `None` if the string is invalid.
5fn parse_us_date(s: &str) -> Option<NaiveDate> {
6    NaiveDate::parse_from_str(s.trim(), "%m/%d/%Y").ok()
7}
8
9/// Returns the end of the month for the given date.
10/// If input is `None`, uses today's date.
11/// If input is a `&str`, tries to parse it as "MM/DD/YYYY".
12pub fn eom<T: Into<DateInput>>(input: T) -> NaiveDate {
13    match input.into() {
14        DateInput::Date(date) => end_of_month(date),
15        DateInput::String(s) => {
16            parse_us_date(&s)
17                .map(end_of_month)
18                .unwrap_or_else(|| end_of_month(Local::now().naive_local().date()))
19        }
20        DateInput::None => end_of_month(Local::now().naive_local().date()),
21    }
22}
23
24fn end_of_month(date: NaiveDate) -> NaiveDate {
25    let year = date.year();
26    let month = date.month();
27    let first_day_next_month = if month == 12 {
28        NaiveDate::from_ymd_opt(year + 1, 1, 1)
29    } else {
30        NaiveDate::from_ymd_opt(year, month + 1, 1)
31    }
32    .expect("Invalid date computation");
33    first_day_next_month - Duration::days(1)
34}
35
36pub enum DateInput {
37    Date(NaiveDate),
38    String(String),
39    None,
40}
41
42impl From<&str> for DateInput {
43    fn from(s: &str) -> Self {
44        DateInput::String(s.to_string())
45    }
46}
47
48impl From<NaiveDate> for DateInput {
49    fn from(date: NaiveDate) -> Self {
50        DateInput::Date(date)
51    }
52}
53
54impl From<Option<NaiveDate>> for DateInput {
55    fn from(opt: Option<NaiveDate>) -> Self {
56        match opt {
57            Some(d) => DateInput::Date(d),
58            None => DateInput::None,
59        }
60    }
61}