Skip to main content

veta_core/
dateparse.rs

1//! Human-readable date parsing for Veta.
2
3use chrono::NaiveDateTime;
4use parse_datetime::parse_datetime;
5
6use crate::Error;
7
8/// Parse a human-readable date string into a SQLite datetime string.
9///
10/// Supports a wide variety of formats including:
11/// - SQLite datetime format: "2026-01-28 12:00:00" (passed through)
12/// - ISO 8601: "2026-01-28T12:00:00Z"
13/// - Relative past: "2 days ago", "1 week ago", "3 hours ago"
14/// - Relative future: "in 2 days", "in 1 week"
15/// - Named: "today", "yesterday", "tomorrow", "now"
16/// - And many more formats supported by the `parse_datetime` crate
17///
18/// Returns an error if the string cannot be parsed.
19pub fn parse_human_date(input: &str) -> Result<String, Error> {
20    let input = input.trim();
21
22    // Try SQLite datetime format first (YYYY-MM-DD HH:MM:SS) - pass through
23    if NaiveDateTime::parse_from_str(input, "%Y-%m-%d %H:%M:%S").is_ok() {
24        return Ok(input.to_string());
25    }
26
27    // Try date-only format (YYYY-MM-DD) - append midnight
28    if chrono::NaiveDate::parse_from_str(input, "%Y-%m-%d").is_ok() {
29        return Ok(format!("{} 00:00:00", input));
30    }
31
32    // Use parse_datetime for everything else
33    match parse_datetime(input) {
34        Ok(zoned) => {
35            // Get the datetime and format as SQLite datetime
36            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        // Just verify it parses without error
73        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}