sleep_utils/
duration_parser.rs1use crate::{Result, SleepError};
2use std::time::Duration;
3
4pub fn parse_sleep_duration(input: &str) -> Result<Duration> {
8 let input = input.trim().to_lowercase();
9
10 if input.is_empty() {
11 return Ok(Duration::ZERO);
12 }
13
14 if let Ok(millis) = input.parse::<isize>() {
16 if millis <= 0 {
17 return Ok(Duration::ZERO);
18 }
19 return Ok(Duration::from_millis(millis as u64));
20 }
21
22 if let Some(duration) = parse_duration_with_unit(&input)? {
24 Ok(duration)
25 } else {
26 Err(SleepError::InvalidDuration(format!(
27 "Invalid sleep duration format: '{}'",
28 input
29 )))
30 }
31}
32
33fn parse_duration_with_unit(input: &str) -> Result<Option<Duration>> {
35 use lazy_static::lazy_static;
36 use regex::Regex;
37
38 lazy_static! {
39 static ref SINGLE_PATTERNS: Vec<(&'static str, f64)> = vec![
41 (r"^(\d+)\s*(ms|millis?|milliseconds?)$", 1.0),
43 (r"^(\d+)\s*(s|sec|seconds?)$", 1000.0),
45 (r"^(\d+)\s*(m|min|minutes?)$", 60_000.0),
47 (r"^(\d+)\s*(h|hr|hours?)$", 3_600_000.0),
49 (r"^(\d+)(ms)$", 1.0),
51 (r"^(\d+)(s)$", 1000.0),
52 (r"^(\d+)(m)$", 60_000.0),
53 (r"^(\d+)(h)$", 3_600_000.0),
54 ];
55
56 static ref FLOAT_PATTERNS: Vec<(&'static str, f64)> = vec![
57 (r"^(\d*\.?\d+)\s*(s|sec|seconds?)$", 1000.0),
58 (r"^(\d*\.?\d+)\s*(m|min|minutes?)$", 60_000.0),
59 (r"^(\d*\.?\d+)(s)$", 1000.0),
60 (r"^(\d*\.?\d+)(m)$", 60_000.0),
61 ];
62 }
63
64 for (pattern, multiplier) in SINGLE_PATTERNS.iter() {
66 let re = Regex::new(pattern).unwrap();
67 if let Some(caps) = re.captures(input) {
68 if let Ok(value) = caps[1].parse::<isize>() {
69 if value <= 0 {
70 return Ok(Some(Duration::ZERO));
71 }
72 let millis = (value as f64 * multiplier) as u64;
73 return Ok(Some(Duration::from_millis(millis)));
74 }
75 }
76 }
77
78 for (pattern, multiplier) in FLOAT_PATTERNS.iter() {
80 let re = Regex::new(pattern).unwrap();
81 if let Some(caps) = re.captures(input) {
82 if let Ok(value) = caps[1].parse::<f64>() {
83 if value <= 0.0 {
84 return Ok(Some(Duration::ZERO));
85 }
86 let millis = (value * multiplier) as u64;
87 return Ok(Some(Duration::from_millis(millis)));
88 }
89 }
90 }
91
92 if let Some(duration) = parse_multiple_units(input)? {
95 return Ok(Some(duration));
96 }
97
98 Ok(None)
99}
100
101fn parse_multiple_units(input: &str) -> Result<Option<Duration>> {
103 use lazy_static::lazy_static;
104 use regex::Regex;
105
106 lazy_static! {
107 static ref MULTI_UNIT_PATTERN: Regex = Regex::new(r"(?i)(\d+)\s*([a-z]+)").unwrap();
109 }
110
111 let mut total_millis: u64 = 0;
112 let mut found_any = false;
113 let mut has_positive_value = false;
114
115 for caps in MULTI_UNIT_PATTERN.captures_iter(input) {
116 let value: u64 = match caps[1].parse() {
117 Ok(v) => v,
118 Err(_) => continue,
119 };
120
121 let unit = &caps[2].to_lowercase();
122 let multiplier = match unit.as_str() {
123 "ms" | "milli" | "millis" | "millisecond" | "milliseconds" => 1,
124 "s" | "sec" | "second" | "seconds" => 1000,
125 "m" | "min" | "minute" | "minutes" => 60_000,
126 "h" | "hr" | "hour" | "hours" => 3_600_000,
127 _ => continue, };
129
130 total_millis += value * multiplier;
131 found_any = true;
132 if value > 0 {
133 has_positive_value = true;
134 }
135 }
136
137 if found_any {
138 if has_positive_value {
140 Ok(Some(Duration::from_millis(total_millis)))
141 } else {
142 Ok(Some(Duration::ZERO))
143 }
144 } else {
145 Ok(None)
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 #[test]
154 fn test_parse_sleep_duration() -> Result<()> {
155 assert_eq!(parse_sleep_duration("100")?, Duration::from_millis(100));
157 assert_eq!(parse_sleep_duration("100ms")?, Duration::from_millis(100));
158 assert_eq!(parse_sleep_duration("1s")?, Duration::from_secs(1));
159 assert_eq!(parse_sleep_duration("1.5s")?, Duration::from_millis(1500));
160 assert_eq!(parse_sleep_duration("0")?, Duration::ZERO);
161
162 assert_eq!(parse_sleep_duration("1m30s")?, Duration::from_millis(90000));
164 assert_eq!(
165 parse_sleep_duration("1h2m3s")?,
166 Duration::from_millis(3723000)
167 );
168 assert_eq!(
169 parse_sleep_duration("1h 2m 3s")?,
170 Duration::from_millis(3723000)
171 );
172 assert_eq!(
173 parse_sleep_duration("2s500ms")?,
174 Duration::from_millis(2500)
175 );
176 assert_eq!(
177 parse_sleep_duration("1m30s500ms")?,
178 Duration::from_millis(90500)
179 );
180
181 Ok(())
182 }
183
184 #[test]
185 fn test_multiple_units_edge_cases() -> Result<()> {
186 assert_eq!(parse_sleep_duration("1m30s")?, Duration::from_secs(90));
188 assert_eq!(parse_sleep_duration("1h30m")?, Duration::from_secs(5400));
189 assert_eq!(parse_sleep_duration("1h1s")?, Duration::from_secs(3601));
190
191 assert_eq!(parse_sleep_duration("1h 30m")?, Duration::from_secs(5400));
193 assert_eq!(parse_sleep_duration("2m 30s")?, Duration::from_secs(150));
194
195 assert_eq!(parse_sleep_duration("0h0m0s")?, Duration::ZERO);
197 assert_eq!(parse_sleep_duration("0s")?, Duration::ZERO);
198 assert_eq!(parse_sleep_duration("0ms")?, Duration::ZERO);
199
200 Ok(())
201 }
202
203 #[test]
204 fn test_float_numbers_not_matched_as_multi_units() -> Result<()> {
205 assert_eq!(parse_sleep_duration("1.5s")?, Duration::from_millis(1500));
207 assert_eq!(parse_sleep_duration("0.5m")?, Duration::from_millis(30000));
208 assert_eq!(parse_sleep_duration("2.5s")?, Duration::from_millis(2500));
209
210 assert_eq!(
212 parse_sleep_duration("1s500ms")?,
213 Duration::from_millis(1500)
214 );
215 assert_eq!(parse_sleep_duration("1m30s")?, Duration::from_millis(90000));
216
217 Ok(())
218 }
219
220 #[test]
221 fn test_zero_values() -> Result<()> {
222 assert_eq!(parse_sleep_duration("0")?, Duration::ZERO);
224 assert_eq!(parse_sleep_duration("0ms")?, Duration::ZERO);
225 assert_eq!(parse_sleep_duration("0s")?, Duration::ZERO);
226 assert_eq!(parse_sleep_duration("0m")?, Duration::ZERO);
227 assert_eq!(parse_sleep_duration("0h")?, Duration::ZERO);
228 assert_eq!(parse_sleep_duration("0h0m0s")?, Duration::ZERO);
229 assert_eq!(parse_sleep_duration("0s0ms")?, Duration::ZERO);
230
231 Ok(())
232 }
233}