aven 0.1.12

Local-first task manager CLI and sync server
Documentation
use chrono::NaiveDate;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum DueState {
    None,
    Future(i64),
    Today,
    Overdue(i64),
}

impl DueState {
    pub(crate) fn needs_action(self) -> bool {
        matches!(self, Self::Today | Self::Overdue(_))
    }

    pub(crate) fn score(self) -> i32 {
        match self {
            Self::Today | Self::Overdue(_) => 40,
            Self::Future(days) if (1..=7).contains(&days) => (5 * (8 - days)) as i32,
            Self::None | Self::Future(_) => 0,
        }
    }
}

pub(crate) fn due_state(due_on: &str, today: NaiveDate) -> DueState {
    let Ok(due) = NaiveDate::parse_from_str(due_on, "%Y-%m-%d") else {
        return DueState::None;
    };
    let days = due.signed_duration_since(today).num_days();
    match days {
        0 => DueState::Today,
        1.. => DueState::Future(days),
        _ => DueState::Overdue(-days),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn today() -> NaiveDate {
        NaiveDate::from_ymd_opt(2026, 7, 16).unwrap()
    }

    #[test]
    fn classifies_calendar_deadlines() {
        assert_eq!(due_state("", today()), DueState::None);
        assert_eq!(due_state("2026-07-15", today()), DueState::Overdue(1));
        assert_eq!(due_state("2026-07-16", today()), DueState::Today);
        assert_eq!(due_state("2026-07-18", today()), DueState::Future(2));
    }

    #[test]
    fn scores_only_due_week_and_late_states() {
        assert_eq!(DueState::Future(8).score(), 0);
        assert_eq!(DueState::Future(7).score(), 5);
        assert_eq!(DueState::Future(1).score(), 35);
        assert_eq!(DueState::Today.score(), 40);
        assert_eq!(DueState::Overdue(30).score(), 40);
    }
}