1use chrono::NaiveDateTime;
4use parse_datetime::parse_datetime;
5
6use crate::Error;
7
8pub fn parse_human_date(input: &str) -> Result<String, Error> {
20 let input = input.trim();
21
22 if NaiveDateTime::parse_from_str(input, "%Y-%m-%d %H:%M:%S").is_ok() {
24 return Ok(input.to_string());
25 }
26
27 if chrono::NaiveDate::parse_from_str(input, "%Y-%m-%d").is_ok() {
29 return Ok(format!("{} 00:00:00", input));
30 }
31
32 match parse_datetime(input) {
34 Ok(zoned) => {
35 let dt = zoned.datetime();
37 Ok(format!(
38 "{:04}-{:02}-{:02} {:02}:{:02}:{:02}",
39 dt.year(),
40 dt.month(),
41 dt.day(),
42 dt.hour(),
43 dt.minute(),
44 dt.second()
45 ))
46 }
47 Err(_) => Err(Error::Validation(format!(
48 "Could not parse date: '{}'. Try formats like '2 days ago', 'yesterday', or '2024-01-28'.",
49 input
50 ))),
51 }
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57
58 #[test]
59 fn test_sqlite_format_passthrough() {
60 let result = parse_human_date("2026-01-28 12:30:45").unwrap();
61 assert_eq!(result, "2026-01-28 12:30:45");
62 }
63
64 #[test]
65 fn test_date_only() {
66 let result = parse_human_date("2026-01-28").unwrap();
67 assert_eq!(result, "2026-01-28 00:00:00");
68 }
69
70 #[test]
71 fn test_relative_days_ago() {
72 let result = parse_human_date("2 days ago");
74 assert!(result.is_ok());
75 }
76
77 #[test]
78 fn test_yesterday() {
79 let result = parse_human_date("yesterday");
80 assert!(result.is_ok());
81 }
82
83 #[test]
84 fn test_today() {
85 let result = parse_human_date("today");
86 assert!(result.is_ok());
87 }
88
89 #[test]
90 fn test_now() {
91 let result = parse_human_date("now");
92 assert!(result.is_ok());
93 }
94
95 #[test]
96 fn test_invalid() {
97 let result = parse_human_date("not a date");
98 assert!(result.is_err());
99 }
100}