todo-txt 4.2.0

Parser for the todo.txt format
Documentation
pub use chrono::NaiveDate as Date;

#[must_use]
pub fn today() -> Date {
    chrono::offset::Local::now().date_naive()
}

pub fn parse(s: &str) -> Option<Date> {
    if let Ok(date) = crate::Date::parse_from_str(s, "%Y-%m-%d") {
        return Some(date);
    }

    static REGEX: std::sync::OnceLock<regex::Regex> = std::sync::OnceLock::new();

    let regex = REGEX.get_or_init(|| {
        regex::Regex::new(
            "(?P<sign>\\+|-)?(?P<nth>\\d+)(?P<unit>m(onths?)?|d(ays?)?|w(eeks?)?|y(ears?)?)",
        )
        .unwrap()
    });

    if let Some(captures) = regex.captures(s) {
        let Ok(nth) = captures["nth"].parse() else {
            return None;
        };

        let mut days = match captures["unit"].chars().next() {
            Some('d') => nth,
            Some('w') => nth * 7,
            Some('m') => (nth as f32 * 30.5) as i64,
            Some('y') => (nth as f32 * 365.25) as i64,
            _ => return None,
        };

        let sign = captures.name("sign").as_ref().map(regex::Match::as_str);
        if sign == Some("-") {
            days *= -1;
        }

        let delta = chrono::TimeDelta::days(days);

        return Some(today() + delta);
    }

    None
}