use chrono::{DateTime, Utc};
use mempill_types::time::parse_valid_time_date;
use crate::ergonomic::MempillDxError;
const HINT: &str =
"Use YYYY, YYYY-MM, YYYY-MM-DD, or RFC3339 (e.g. 2026-01-01T00:00:00Z). \
Natural-language dates must be resolved by the caller before passing to remember().";
pub fn parse_lenient_date(s: &str) -> Result<DateTime<Utc>, MempillDxError> {
let s = s.trim();
if s.is_empty() {
return Err(MempillDxError::UnparsableDate { input: s.to_string(), hint: HINT });
}
parse_valid_time_date(s)
.map(|(dt, _gran)| dt)
.ok_or_else(|| MempillDxError::UnparsableDate { input: s.to_string(), hint: HINT })
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::{Datelike, Timelike};
#[test]
fn parse_year_only() {
let dt = parse_lenient_date("2026").unwrap();
assert_eq!(dt.year(), 2026);
assert_eq!(dt.month(), 1);
assert_eq!(dt.day(), 1);
}
#[test]
fn parse_year_month() {
let dt = parse_lenient_date("2026-06").unwrap();
assert_eq!(dt.year(), 2026);
assert_eq!(dt.month(), 6);
assert_eq!(dt.day(), 1);
}
#[test]
fn parse_full_date() {
let dt = parse_lenient_date("2026-06-15").unwrap();
assert_eq!(dt.year(), 2026);
assert_eq!(dt.month(), 6);
assert_eq!(dt.day(), 15);
}
#[test]
fn parse_rfc3339_passthrough() {
let input = "2026-06-15T12:30:00Z";
let dt = parse_lenient_date(input).unwrap();
assert_eq!(dt.year(), 2026);
assert_eq!(dt.month(), 6);
assert_eq!(dt.day(), 15);
assert_eq!(dt.hour(), 12);
}
#[test]
fn unparsable_natural_language() {
let err = parse_lenient_date("March 2020").unwrap_err();
match err {
MempillDxError::UnparsableDate { input, hint } => {
assert_eq!(input, "March 2020");
assert!(!hint.contains("premature end of input"), "hint must not say 'premature end of input'");
assert!(hint.contains("YYYY"), "hint must mention YYYY format");
}
other => panic!("expected UnparsableDate, got {other:?}"),
}
}
#[test]
fn unparsable_empty_string() {
let err = parse_lenient_date("").unwrap_err();
assert!(matches!(err, MempillDxError::UnparsableDate { .. }));
}
#[test]
fn midnight_utc_for_all_non_rfc3339_forms() {
for s in &["2020", "2020-03", "2020-03-15"] {
let dt = parse_lenient_date(s).unwrap();
assert_eq!(dt.hour(), 0);
assert_eq!(dt.minute(), 0);
assert_eq!(dt.second(), 0);
}
}
}