human_readable_time/
lib.rs

1use crate::errors::ParseHumanReadableDurationError;
2#[cfg(feature = "chrono")]
3use crate::traits::AsDuration;
4use crate::traits::{AsDays, AsHours, AsMinutes, AsSeconds};
5use std::str::FromStr;
6
7// the modules we have in this crate
8pub mod errors;
9pub mod traits;
10
11/// A data structure for parsing and managing a human readable duration representation
12pub struct HumanReadableDuration {
13    time_in_seconds: u64,
14}
15
16impl AsSeconds for HumanReadableDuration {
17    /// Get the duration time in seconds
18    ///
19    /// # Example
20    /// ```
21    /// use std::str::FromStr;
22    /// use human_readable_time::HumanReadableDuration;
23    /// use human_readable_time::traits::AsSeconds;
24    ///
25    /// let duration = HumanReadableDuration::from_str("10s");
26    ///
27    /// assert_eq!(10, duration.unwrap().as_seconds());
28    /// ```
29    fn as_seconds(&self) -> u64 {
30        self.time_in_seconds
31    }
32}
33
34impl AsMinutes for HumanReadableDuration {
35    /// Get the duration time in full minutes
36    ///
37    /// # Example
38    /// ```
39    /// use std::str::FromStr;
40    /// use human_readable_time::HumanReadableDuration;
41    /// use human_readable_time::traits::AsMinutes;
42    ///
43    /// let duration = HumanReadableDuration::from_str("65s");
44    ///
45    /// assert_eq!(1, duration.unwrap().as_minutes());
46    /// ```
47    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    /// Get the duration time in full hours
56    ///
57    /// # Example
58    /// ```
59    /// use std::str::FromStr;
60    /// use human_readable_time::HumanReadableDuration;
61    /// use human_readable_time::traits::AsHours;
62    ///
63    /// let duration = HumanReadableDuration::from_str("65m");
64    ///
65    /// assert_eq!(1, duration.unwrap().as_hours());
66    /// ```
67    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    /// Get the duration time in full days
76    ///
77    /// # Example
78    /// ```
79    /// use std::str::FromStr;
80    /// use human_readable_time::HumanReadableDuration;
81    /// use human_readable_time::traits::AsDays;
82    ///
83    /// let duration = HumanReadableDuration::from_str("48h");
84    ///
85    /// assert_eq!(2, duration.unwrap().as_days());
86    /// ```
87    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    /// Convert the object to a [`chrono::Duration`]  representation.
97    ///
98    /// # Example
99    /// ```
100    /// use std::str::FromStr;
101    /// use human_readable_time::HumanReadableDuration;
102    /// use human_readable_time::traits::AsDuration;
103    ///
104    /// let duration = HumanReadableDuration::from_str("65m").unwrap();
105    ///
106    /// assert_eq!(3900, duration.as_duration().num_seconds());
107    /// assert_eq!(1, duration.as_duration().num_hours());
108    /// ```
109    fn as_duration(&self) -> chrono::Duration {
110        chrono::Duration::seconds(self.time_in_seconds as i64) // TODO: check if `time_in_seconds` will fit in a i64
111    }
112}
113
114/// The internally used time units which are supported.
115enum 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        // ensure that the string has to be at least one character long
127        if s.len() < 1 {
128            return Err(());
129        }
130
131        // match the first character to the corresponding unit
132        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
142/// A tuple of a time unit and the corresponding value (only for internal use).
143struct InternalTime(u64, InternalTimeUnit);
144
145/// A method for extracting the containing time information from a string. This method should
146/// only be used internally.
147fn extract_time_information(value: &str) -> Vec<InternalTime> {
148    use lazy_static::lazy_static;
149    use regex::Regex;
150
151    // compile the regular expression for extracting the supported timings
152    lazy_static! {
153        static ref TIME_REGEX: Regex = Regex::from_str(r"([0-9]+)([dhms]){1}").unwrap();
154    }
155
156    // collect all found matches
157    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    // return the found matches
167    found_matches
168}
169
170/// Parse a value from a string
171///
172/// `FromStr`'s `from_str` method is often used implicitly, through
173/// `str`'s `parse` method. See `parse`'s documentation for examples.
174impl FromStr for HumanReadableDuration {
175    type Err = ParseHumanReadableDurationError;
176
177    /// Parses a string `s` to return a value of the [`HumanReadableDuration`] type.
178    ///
179    /// If parsing succeeds, return the value inside [`Ok`], otherwise
180    /// when the string is ill-formatted return an error specific to the
181    /// inside [`Err`].
182    ///
183    /// # Examples
184    /// ```
185    /// use std::str::FromStr;
186    /// use human_readable_time::HumanReadableDuration;
187    /// use human_readable_time::traits::AsSeconds;
188    ///
189    /// let s = "50s";
190    /// let x = HumanReadableDuration::from_str(s).unwrap();
191    ///
192    /// assert_eq!(50, x.as_seconds());
193    /// ```
194    fn from_str(value: &str) -> Result<Self, Self::Err> {
195        // try to get the time information from the passed string
196        let time_information = extract_time_information(value);
197
198        // if we could not extract any information, return an error
199        if time_information.is_empty() {
200            return Err(ParseHumanReadableDurationError);
201        }
202
203        // sum up the seconds and return corresponding object
204        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
219/// Used to do value-to-value conversions while consuming the input value. It is the reciprocal of
220/// [`Into`].
221impl From<u64> for HumanReadableDuration {
222    /// Create an instance for [`HumanReadableDuration`] from a `u64` field representing the seconds
223    ///
224    /// # Example
225    /// ```
226    /// use human_readable_time::HumanReadableDuration;
227    /// use human_readable_time::traits::{AsMinutes, AsSeconds};
228    ///
229    /// let seconds: u64 = 300;
230    /// let representation = HumanReadableDuration::from(seconds);
231    ///
232    /// assert_eq!(300, representation.as_seconds());
233    /// assert_eq!(5, representation.as_minutes());
234    /// ```
235    fn from(value: u64) -> Self {
236        HumanReadableDuration {
237            time_in_seconds: value,
238        }
239    }
240}
241
242/// Used to do value-to-value conversions while consuming the input value. It is the reciprocal of
243/// [`Into`].
244impl From<u32> for HumanReadableDuration {
245    /// Create an instance for [`HumanReadableDuration`] from a `u32` field representing the seconds
246    ///
247    /// # Example
248    /// ```
249    /// use human_readable_time::HumanReadableDuration;
250    /// use human_readable_time::traits::{AsMinutes, AsSeconds};
251    ///
252    /// let seconds: u32 = 300;
253    /// let representation = HumanReadableDuration::from(seconds);
254    ///
255    /// assert_eq!(300, representation.as_seconds());
256    /// assert_eq!(5, representation.as_minutes());
257    /// ```
258    fn from(value: u32) -> Self {
259        HumanReadableDuration {
260            time_in_seconds: value as u64,
261        }
262    }
263}
264
265/// Used to do value-to-value conversions while consuming the input value. It is the reciprocal of
266/// [`Into`].
267impl From<u16> for HumanReadableDuration {
268    /// Create an instance for [`HumanReadableDuration`] from a `u16` field representing the seconds
269    ///
270    /// # Example
271    /// ```
272    /// use human_readable_time::HumanReadableDuration;
273    /// use human_readable_time::traits::{AsMinutes, AsSeconds};
274    ///
275    /// let seconds: u16 = 300;
276    /// let representation = HumanReadableDuration::from(seconds);
277    ///
278    /// assert_eq!(300, representation.as_seconds());
279    /// assert_eq!(5, representation.as_minutes());
280    /// ```
281    fn from(value: u16) -> Self {
282        HumanReadableDuration {
283            time_in_seconds: value as u64,
284        }
285    }
286}
287
288/// Used to do value-to-value conversions while consuming the input value. It is the reciprocal of
289/// [`Into`].
290impl From<u8> for HumanReadableDuration {
291    /// Create an instance for [`HumanReadableDuration`] from a `u8` field representing the seconds
292    ///
293    /// # Example
294    /// ```
295    /// use human_readable_time::HumanReadableDuration;
296    /// use human_readable_time::traits::{AsMinutes, AsSeconds};
297    ///
298    /// let seconds: u8 = 120;
299    /// let representation = HumanReadableDuration::from(seconds);
300    ///
301    /// assert_eq!(120, representation.as_seconds());
302    /// assert_eq!(2, representation.as_minutes());
303    /// ```
304    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}