1use crate::errors::ParseHumanReadableDurationError;
2#[cfg(feature = "chrono")]
3use crate::traits::AsDuration;
4use crate::traits::{AsDays, AsHours, AsMinutes, AsSeconds};
5use std::str::FromStr;
6
7pub mod errors;
9pub mod traits;
10
11pub struct HumanReadableDuration {
13 time_in_seconds: u64,
14}
15
16impl AsSeconds for HumanReadableDuration {
17 fn as_seconds(&self) -> u64 {
30 self.time_in_seconds
31 }
32}
33
34impl AsMinutes for HumanReadableDuration {
35 fn as_minutes(&self) -> u64 {
48 let divisor = self.time_in_seconds as f32;
49 let result = divisor / 60.0f32;
50 return result as u64;
51 }
52}
53
54impl AsHours for HumanReadableDuration {
55 fn as_hours(&self) -> u64 {
68 let divisor = self.time_in_seconds as f32;
69 let result = divisor / 3600.0f32;
70 return result as u64;
71 }
72}
73
74impl AsDays for HumanReadableDuration {
75 fn as_days(&self) -> u64 {
88 let divisor = self.time_in_seconds as f32;
89 let result = divisor / 86400.0f32;
90 return result as u64;
91 }
92}
93
94#[cfg(feature = "chrono")]
95impl AsDuration for HumanReadableDuration {
96 fn as_duration(&self) -> chrono::Duration {
110 chrono::Duration::seconds(self.time_in_seconds as i64) }
112}
113
114enum InternalTimeUnit {
116 Seconds,
117 Minutes,
118 Hours,
119 Days,
120}
121
122impl FromStr for InternalTimeUnit {
123 type Err = ();
124
125 fn from_str(s: &str) -> Result<Self, Self::Err> {
126 if s.len() < 1 {
128 return Err(());
129 }
130
131 match s.to_lowercase().chars().next().unwrap() {
133 's' => Ok(InternalTimeUnit::Seconds),
134 'm' => Ok(InternalTimeUnit::Minutes),
135 'h' => Ok(InternalTimeUnit::Hours),
136 'd' => Ok(InternalTimeUnit::Days),
137 _ => Err(()),
138 }
139 }
140}
141
142struct InternalTime(u64, InternalTimeUnit);
144
145fn extract_time_information(value: &str) -> Vec<InternalTime> {
148 use lazy_static::lazy_static;
149 use regex::Regex;
150
151 lazy_static! {
153 static ref TIME_REGEX: Regex = Regex::from_str(r"([0-9]+)([dhms]){1}").unwrap();
154 }
155
156 let mut found_matches = vec![];
158 for capture in TIME_REGEX.captures_iter(value) {
159 if let Ok(time) = u64::from_str(&capture[1]) {
160 if let Ok(unit) = InternalTimeUnit::from_str(&capture[2]) {
161 found_matches.push(InternalTime(time, unit))
162 }
163 }
164 }
165
166 found_matches
168}
169
170impl FromStr for HumanReadableDuration {
175 type Err = ParseHumanReadableDurationError;
176
177 fn from_str(value: &str) -> Result<Self, Self::Err> {
195 let time_information = extract_time_information(value);
197
198 if time_information.is_empty() {
200 return Err(ParseHumanReadableDurationError);
201 }
202
203 let mut seconds = 0;
205 for current_time_object in time_information {
206 match current_time_object.1 {
207 InternalTimeUnit::Seconds => seconds += current_time_object.0,
208 InternalTimeUnit::Minutes => seconds += current_time_object.0 * 60,
209 InternalTimeUnit::Hours => seconds += current_time_object.0 * 3600,
210 InternalTimeUnit::Days => seconds += current_time_object.0 * 86400,
211 }
212 }
213 return Ok(HumanReadableDuration {
214 time_in_seconds: seconds,
215 });
216 }
217}
218
219impl From<u64> for HumanReadableDuration {
222 fn from(value: u64) -> Self {
236 HumanReadableDuration {
237 time_in_seconds: value,
238 }
239 }
240}
241
242impl From<u32> for HumanReadableDuration {
245 fn from(value: u32) -> Self {
259 HumanReadableDuration {
260 time_in_seconds: value as u64,
261 }
262 }
263}
264
265impl From<u16> for HumanReadableDuration {
268 fn from(value: u16) -> Self {
282 HumanReadableDuration {
283 time_in_seconds: value as u64,
284 }
285 }
286}
287
288impl From<u8> for HumanReadableDuration {
291 fn from(value: u8) -> Self {
305 HumanReadableDuration {
306 time_in_seconds: value as u64,
307 }
308 }
309}
310
311#[cfg(test)]
312mod tests {
313 use crate::traits::{AsDays, AsHours, AsMinutes, AsSeconds};
314 use crate::HumanReadableDuration;
315 use std::str::FromStr;
316
317 #[test]
318 fn from_u32_works() {
319 let representation = HumanReadableDuration::from(300 as u32);
320 assert_eq!(300, representation.as_seconds());
321 assert_eq!(5, representation.as_minutes());
322 }
323
324 #[test]
325 fn from_u64_works() {
326 let representation = HumanReadableDuration::from(300 as u64);
327 assert_eq!(300, representation.as_seconds());
328 assert_eq!(5, representation.as_minutes());
329 }
330
331 #[test]
332 fn from_str_with_empty_string_will_be_handled_gracefully() {
333 let representation = HumanReadableDuration::from_str("");
334 assert_eq!(true, representation.is_err());
335 }
336
337 #[test]
338 fn from_str_10_s_will_be_handled_gracefully() {
339 let representation = HumanReadableDuration::from_str("10 s");
340 assert_eq!(true, representation.is_err());
341 }
342
343 #[test]
344 fn from_str_10s_works() {
345 let representation = HumanReadableDuration::from_str("10s");
346 assert_eq!(true, representation.is_ok());
347 assert_eq!(10, representation.as_ref().unwrap().as_seconds());
348 assert_eq!(0, representation.as_ref().unwrap().as_minutes());
349 }
350
351 #[test]
352 fn from_str_60s_works() {
353 let representation = HumanReadableDuration::from_str("60s");
354 assert_eq!(true, representation.is_ok());
355 assert_eq!(60, representation.as_ref().unwrap().as_seconds());
356 assert_eq!(1, representation.as_ref().unwrap().as_minutes());
357 }
358
359 #[test]
360 fn from_str_61s_works() {
361 let representation = HumanReadableDuration::from_str("61s");
362 assert_eq!(true, representation.is_ok());
363 assert_eq!(61, representation.as_ref().unwrap().as_seconds());
364 assert_eq!(1, representation.as_ref().unwrap().as_minutes());
365 }
366
367 #[test]
368 fn from_str_5_m_will_be_handled_gracefully() {
369 let representation = HumanReadableDuration::from_str("5 m");
370 assert_eq!(true, representation.is_err());
371 }
372
373 #[test]
374 fn from_str_5m_works() {
375 let representation = HumanReadableDuration::from_str("5m");
376 assert_eq!(true, representation.is_ok());
377 assert_eq!(300, representation.as_ref().unwrap().as_seconds());
378 assert_eq!(5, representation.as_ref().unwrap().as_minutes());
379 }
380
381 #[test]
382 fn from_str_60m_works() {
383 let representation = HumanReadableDuration::from_str("60m");
384 assert_eq!(true, representation.is_ok());
385 assert_eq!(3600, representation.as_ref().unwrap().as_seconds());
386 assert_eq!(60, representation.as_ref().unwrap().as_minutes());
387 assert_eq!(1, representation.as_ref().unwrap().as_hours());
388 }
389
390 #[test]
391 fn from_str_61m_works() {
392 let representation = HumanReadableDuration::from_str("61m");
393 assert_eq!(true, representation.is_ok());
394 assert_eq!(3660, representation.as_ref().unwrap().as_seconds());
395 assert_eq!(61, representation.as_ref().unwrap().as_minutes());
396 assert_eq!(1, representation.as_ref().unwrap().as_hours());
397 }
398
399 #[test]
400 fn from_str_5_h_will_be_handled_gracefully() {
401 let representation = HumanReadableDuration::from_str("5 h");
402 assert_eq!(true, representation.is_err());
403 }
404
405 #[test]
406 fn from_str_5h_works() {
407 let representation = HumanReadableDuration::from_str("5h");
408 assert_eq!(true, representation.is_ok());
409 assert_eq!(18000, representation.as_ref().unwrap().as_seconds());
410 assert_eq!(300, representation.as_ref().unwrap().as_minutes());
411 assert_eq!(5, representation.as_ref().unwrap().as_hours());
412 }
413
414 #[test]
415 fn from_str_24h_works() {
416 let representation = HumanReadableDuration::from_str("24h");
417 assert_eq!(true, representation.is_ok());
418 assert_eq!(86400, representation.as_ref().unwrap().as_seconds());
419 assert_eq!(1440, representation.as_ref().unwrap().as_minutes());
420 assert_eq!(24, representation.as_ref().unwrap().as_hours());
421 }
422
423 #[test]
424 fn from_str_25h_works() {
425 let representation = HumanReadableDuration::from_str("25h");
426 assert_eq!(true, representation.is_ok());
427 assert_eq!(90000, representation.as_ref().unwrap().as_seconds());
428 assert_eq!(1500, representation.as_ref().unwrap().as_minutes());
429 assert_eq!(25, representation.as_ref().unwrap().as_hours());
430 }
431
432 #[test]
433 fn from_str_5_d_will_be_handled_gracefully() {
434 let representation = HumanReadableDuration::from_str("5 d");
435 assert_eq!(true, representation.is_err());
436 }
437
438 #[test]
439 fn from_str_5d_works() {
440 let representation = HumanReadableDuration::from_str("5d");
441 assert_eq!(true, representation.is_ok());
442 assert_eq!(432000, representation.as_ref().unwrap().as_seconds());
443 assert_eq!(7200, representation.as_ref().unwrap().as_minutes());
444 assert_eq!(120, representation.as_ref().unwrap().as_hours());
445 assert_eq!(5, representation.as_ref().unwrap().as_days());
446 }
447
448 #[test]
449 fn from_str_32d_works() {
450 let representation = HumanReadableDuration::from_str("32d");
451 assert_eq!(true, representation.is_ok());
452 assert_eq!(2764800, representation.as_ref().unwrap().as_seconds());
453 assert_eq!(46080, representation.as_ref().unwrap().as_minutes());
454 assert_eq!(768, representation.as_ref().unwrap().as_hours());
455 assert_eq!(32, representation.as_ref().unwrap().as_days());
456 }
457
458 #[test]
459 fn from_str_4_m_10_s_will_be_handled_gracefully() {
460 let representation = HumanReadableDuration::from_str("4 m 10 s");
461 assert_eq!(true, representation.is_err());
462 }
463
464 #[test]
465 fn from_str_4m_10s_works() {
466 let representation = HumanReadableDuration::from_str("4m 10s");
467 assert_eq!(true, representation.is_ok());
468 assert_eq!(250, representation.as_ref().unwrap().as_seconds());
469 assert_eq!(4, representation.as_ref().unwrap().as_minutes());
470 }
471
472 #[test]
473 fn from_str_4m10s_works() {
474 let representation = HumanReadableDuration::from_str("4m10s");
475 assert_eq!(true, representation.is_ok());
476 assert_eq!(250, representation.as_ref().unwrap().as_seconds());
477 assert_eq!(4, representation.as_ref().unwrap().as_minutes());
478 }
479
480 #[test]
481 fn from_str_3m60s_works() {
482 let representation = HumanReadableDuration::from_str("3m60s");
483 assert_eq!(true, representation.is_ok());
484 assert_eq!(240, representation.as_ref().unwrap().as_seconds());
485 assert_eq!(4, representation.as_ref().unwrap().as_minutes());
486 }
487
488 #[test]
489 fn from_str_3m61s_works() {
490 let representation = HumanReadableDuration::from_str("3m61s");
491 assert_eq!(true, representation.is_ok());
492 assert_eq!(241, representation.as_ref().unwrap().as_seconds());
493 assert_eq!(4, representation.as_ref().unwrap().as_minutes());
494 }
495}