use crate::error::MpsError;
use chrono::{Datelike, Duration, Local, NaiveDate, Weekday};
pub fn parse_date(input: &str) -> Result<NaiveDate, MpsError> {
let s = input.trim().to_lowercase();
let today = Local::now().date_naive();
match s.as_str() {
"today" => return Ok(today),
"yesterday" => return Ok(today - Duration::days(1)),
"last week" => return Ok(today - Duration::days(7)),
_ => {}
}
if let Some(rest) = s.strip_suffix(" days ago") {
if let Ok(n) = rest.trim().parse::<i64>() {
return Ok(today - Duration::days(n));
}
}
if let Some(rest) = s.strip_suffix(" day ago") {
if rest.trim() == "1" || rest.trim() == "a" {
return Ok(today - Duration::days(1));
}
}
if let Some(rest) = s.strip_prefix("last ") {
if let Some(wd) = parse_weekday(rest.trim()) {
return Ok(last_weekday(today, wd));
}
}
if let Some(wd) = parse_weekday(&s) {
return Ok(last_weekday(today, wd));
}
if s.len() == 8 && s.chars().all(|c| c.is_ascii_digit()) {
return NaiveDate::parse_from_str(&s, "%Y%m%d")
.map_err(|_| MpsError::DateParseError(input.to_string()));
}
if s.len() == 10 && s.chars().nth(4) == Some('-') {
return NaiveDate::parse_from_str(&s, "%Y-%m-%d")
.map_err(|_| MpsError::DateParseError(input.to_string()));
}
Err(MpsError::DateParseError(input.to_string()))
}
fn parse_weekday(s: &str) -> Option<Weekday> {
match s {
"monday" | "mon" => Some(Weekday::Mon),
"tuesday" | "tue" => Some(Weekday::Tue),
"wednesday" | "wed" => Some(Weekday::Wed),
"thursday" | "thu" => Some(Weekday::Thu),
"friday" | "fri" => Some(Weekday::Fri),
"saturday" | "sat" => Some(Weekday::Sat),
"sunday" | "sun" => Some(Weekday::Sun),
_ => None,
}
}
fn last_weekday(today: NaiveDate, wd: Weekday) -> NaiveDate {
let mut d = today - Duration::days(1);
while d.weekday() != wd {
d -= Duration::days(1);
}
d
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_today() {
assert_eq!(parse_date("today").unwrap(), Local::now().date_naive());
}
#[test]
fn test_yesterday() {
let expected = Local::now().date_naive() - Duration::days(1);
assert_eq!(parse_date("yesterday").unwrap(), expected);
}
#[test]
fn test_yyyymmdd() {
assert_eq!(
parse_date("20260428").unwrap(),
NaiveDate::from_ymd_opt(2026, 4, 28).unwrap()
);
}
#[test]
fn test_yyyy_mm_dd() {
assert_eq!(
parse_date("2026-04-28").unwrap(),
NaiveDate::from_ymd_opt(2026, 4, 28).unwrap()
);
}
#[test]
fn test_n_days_ago() {
let expected = Local::now().date_naive() - Duration::days(3);
assert_eq!(parse_date("3 days ago").unwrap(), expected);
}
#[test]
fn test_last_week() {
let expected = Local::now().date_naive() - Duration::days(7);
assert_eq!(parse_date("last week").unwrap(), expected);
}
#[test]
fn test_invalid() {
assert!(parse_date("gibberish date string").is_err());
}
}