intent_engine/
time_utils.rs1use crate::error::{IntentError, Result};
6use chrono::{DateTime, Duration, Utc};
7
8pub fn parse_duration(duration: &str) -> Result<DateTime<Utc>> {
37 let duration = duration.trim();
38
39 if duration.len() < 2 {
40 return Err(IntentError::InvalidInput(
41 "Duration must be in format like '7d', '24h', '30m', '5w', or '10s'".to_string(),
42 ));
43 }
44
45 let (num_str, unit) = duration.split_at(duration.len() - 1);
46 let num: i64 = num_str.parse().map_err(|_| {
47 IntentError::InvalidInput(format!("Invalid number in duration: '{}'", num_str))
48 })?;
49
50 let offset = match unit {
51 "d" => Duration::days(num),
52 "h" => Duration::hours(num),
53 "m" => Duration::minutes(num),
54 "s" => Duration::seconds(num),
55 "w" => Duration::weeks(num),
56 _ => {
57 return Err(IntentError::InvalidInput(format!(
58 "Invalid duration unit '{}'. Use 'd' (days), 'h' (hours), 'm' (minutes), 's' (seconds), or 'w' (weeks)",
59 unit
60 )))
61 }
62 };
63
64 Ok(Utc::now() - offset)
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 #[test]
72 fn test_parse_duration_days() {
73 let result = parse_duration("7d").unwrap();
74 let expected_diff = Duration::days(7);
75 let actual_diff = Utc::now() - result;
76
77 assert!((actual_diff - expected_diff).num_seconds().abs() <= 1);
79 }
80
81 #[test]
82 fn test_parse_duration_hours() {
83 let result = parse_duration("24h").unwrap();
84 let expected_diff = Duration::hours(24);
85 let actual_diff = Utc::now() - result;
86
87 assert!((actual_diff - expected_diff).num_seconds().abs() <= 1);
88 }
89
90 #[test]
91 fn test_parse_duration_minutes() {
92 let result = parse_duration("30m").unwrap();
93 let expected_diff = Duration::minutes(30);
94 let actual_diff = Utc::now() - result;
95
96 assert!((actual_diff - expected_diff).num_seconds().abs() <= 1);
97 }
98
99 #[test]
100 fn test_parse_duration_seconds() {
101 let result = parse_duration("10s").unwrap();
102 let expected_diff = Duration::seconds(10);
103 let actual_diff = Utc::now() - result;
104
105 assert!((actual_diff - expected_diff).num_seconds().abs() <= 1);
106 }
107
108 #[test]
109 fn test_parse_duration_weeks() {
110 let result = parse_duration("2w").unwrap();
111 let expected_diff = Duration::weeks(2);
112 let actual_diff = Utc::now() - result;
113
114 assert!((actual_diff - expected_diff).num_seconds().abs() <= 1);
115 }
116
117 #[test]
118 fn test_parse_duration_with_whitespace() {
119 let result = parse_duration(" 7d ").unwrap();
120 let expected_diff = Duration::days(7);
121 let actual_diff = Utc::now() - result;
122
123 assert!((actual_diff - expected_diff).num_seconds().abs() <= 1);
124 }
125
126 #[test]
127 fn test_parse_duration_invalid_number() {
128 let result = parse_duration("abc d");
129 assert!(matches!(result, Err(IntentError::InvalidInput(_))));
130 }
131
132 #[test]
133 fn test_parse_duration_invalid_unit() {
134 let result = parse_duration("7x");
135 assert!(matches!(result, Err(IntentError::InvalidInput(_))));
136
137 if let Err(IntentError::InvalidInput(msg)) = result {
138 assert!(msg.contains("Invalid duration unit"));
139 }
140 }
141
142 #[test]
143 fn test_parse_duration_too_short() {
144 let result = parse_duration("7");
145 assert!(matches!(result, Err(IntentError::InvalidInput(_))));
146 }
147
148 #[test]
149 fn test_parse_duration_empty() {
150 let result = parse_duration("");
151 assert!(matches!(result, Err(IntentError::InvalidInput(_))));
152 }
153}