//! Date/time parsing helpers for user-supplied schedule inputs. /// Parse an optional datetime string for scheduled publishing. /// /// Accepts ISO 8601 (RFC 3339) or HTML `datetime-local` format (`%Y-%m-%dT%H:%M`). /// Returns `Some(Some(dt))` for a valid datetime, `Some(None)` for empty string /// (clear schedule), or `None` for absent input (no change). pub fn parse_schedule_datetime(s: Option<&str>) -> Option>> { s.map(|s| { if s.is_empty() { None } else { chrono::DateTime::parse_from_rfc3339(s) .map(|dt| dt.with_timezone(&chrono::Utc)) .or_else(|_| { chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M") .map(|naive| naive.and_utc()) }) .ok() } }) } #[cfg(test)] mod tests { use super::*; #[test] fn parse_schedule_datetime_none_input() { assert!(parse_schedule_datetime(None).is_none()); } #[test] fn parse_schedule_datetime_empty_clears() { assert_eq!(parse_schedule_datetime(Some("")), Some(None)); } #[test] fn parse_schedule_datetime_rfc3339() { assert!( parse_schedule_datetime(Some("2026-04-29T12:00:00Z")) .unwrap() .is_some() ); } #[test] fn parse_schedule_datetime_html_local() { assert!( parse_schedule_datetime(Some("2026-04-29T12:00")) .unwrap() .is_some() ); } }