duration_string/
lib.rs

1//! `duration-string` is a library to convert from `String` to `Duration` and vice-versa.
2//!
3//! ![Crates.io](https://img.shields.io/crates/v/duration-string.svg)
4//! ![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)
5//!
6//! Takes a `String` such as `100ms`, `2s`, `5m 30s`, `1h10m` and converts it into a `Duration`.
7//!
8//! Takes a `Duration` and converts it into `String`.
9//!
10//! The `String` format is a multiply of `[0-9]+(ns|us|ms|[smhdwy])`
11//!
12//! ## Example
13//!
14//! `String` to `Duration`:
15//!
16//! ```rust
17//! use std::convert::TryFrom;
18//! use duration_string::DurationString;
19//! use std::time::Duration;
20//!
21//! let d: Duration = DurationString::try_from(String::from("100ms")).unwrap().into();
22//! assert_eq!(d, Duration::from_millis(100));
23//!
24//! // Alternatively
25//! let d: Duration = "100ms".parse::<DurationString>().unwrap().into();
26//! assert_eq!(d, Duration::from_millis(100));
27//! ```
28//!
29//! `Duration` to `String`:
30//!
31//! ```rust
32//! use std::convert::TryFrom;
33//! use duration_string::*;
34//! use std::time::Duration;
35//!
36//! let d: String = DurationString::from(Duration::from_millis(100)).into();
37//! assert_eq!(d, String::from("100ms"));
38//! ```
39//!
40//! ## Serde support
41//!
42//! You can enable _serialization/deserialization_ support by adding the feature `serde`
43//!
44//! - Add `serde` feature
45//!
46//!    ```toml
47//!    duration-string = { version = "0.5.2", features = ["serde"] }
48//!    ```
49//!
50//! - Add derive to struct
51//!
52//!    ```ignore
53//!    use duration_string::DurationString;
54//!    use serde::{Deserialize, Serialize};
55//!
56//!    #[derive(Serialize, Deserialize)]
57//!    struct Foo {
58//!      duration: DurationString
59//!    }
60//!    ```
61//!
62#![cfg_attr(feature = "serde", doc = "```rust")]
63#![cfg_attr(not(feature = "serde"), doc = "```ignore")]
64//! ```
65//! use duration_string::DurationString;
66//! use serde::{Deserialize, Serialize};
67//! use serde_json;
68//!
69//! #[derive(Serialize, Deserialize)]
70//! struct SerdeSupport {
71//!     t: DurationString,
72//! }
73//! let s = SerdeSupport {
74//!     t: DurationString::from_string(String::from("1m")).unwrap(),
75//! };
76//! assert_eq!(r#"{"t":"1m"}"#, serde_json::to_string(&s).unwrap());
77//! ```
78
79#[cfg(feature = "serde")]
80use serde::de::Unexpected;
81use std::borrow::{Borrow, BorrowMut};
82use std::convert::TryFrom;
83#[cfg(feature = "serde")]
84use std::fmt;
85use std::iter::Sum;
86#[cfg(feature = "serde")]
87use std::marker::PhantomData;
88use std::num::ParseIntError;
89use std::ops::{Add, AddAssign, Deref, DerefMut, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
90use std::str::FromStr;
91use std::time::Duration;
92
93const YEAR_IN_NANO: u128 = 31_556_926_000_000_000;
94const WEEK_IN_NANO: u128 = 604_800_000_000_000;
95const DAY_IN_NANO: u128 = 86_400_000_000_000;
96const HOUR_IN_NANO: u128 = 3_600_000_000_000;
97const MINUTE_IN_NANO: u128 = 60_000_000_000;
98const SECOND_IN_NANO: u128 = 1_000_000_000;
99const MILLISECOND_IN_NANO: u128 = 1_000_000;
100const MICROSECOND_IN_NANO: u128 = 1000;
101
102const HOUR_IN_SECONDS: u32 = 3600;
103const MINUTE_IN_SECONDS: u32 = 60;
104const DAY_IN_SECONDS: u32 = 86_400;
105const WEEK_IN_SECONDS: u32 = 604_800;
106const YEAR_IN_SECONDS: u32 = 31_556_926;
107
108pub type Result<T> = std::result::Result<T, Error>;
109
110#[derive(Clone, Debug, Eq, PartialEq)]
111pub enum Error {
112    Format,
113    Overflow,
114    ParseInt(ParseIntError),
115}
116
117impl std::fmt::Display for Error {
118    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119        match self {
120            Self::Format => write!(
121                f,
122                "missing time duration format, must be multiples of `[0-9]+(ns|us|ms|[smhdwy])`"
123            ),
124            Self::Overflow => write!(f, "number is too large to fit in target type"),
125            Self::ParseInt(err) => write!(f, "{err}"),
126        }
127    }
128}
129
130impl std::error::Error for Error {
131    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
132        match self {
133            Self::Format | Self::Overflow => None,
134            Self::ParseInt(err) => Some(err),
135        }
136    }
137}
138
139impl From<ParseIntError> for Error {
140    fn from(value: ParseIntError) -> Self {
141        Self::ParseInt(value)
142    }
143}
144
145#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
146pub struct DurationString(Duration);
147
148impl DurationString {
149    #[must_use]
150    pub const fn new(duration: Duration) -> DurationString {
151        DurationString(duration)
152    }
153
154    #[allow(clippy::missing_errors_doc)]
155    pub fn from_string(duration: String) -> Result<Self> {
156        DurationString::try_from(duration)
157    }
158}
159
160impl std::fmt::Display for DurationString {
161    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162        let s: String = (*self).into();
163        write!(f, "{s}")
164    }
165}
166
167impl From<DurationString> for Duration {
168    fn from(value: DurationString) -> Self {
169        value.0
170    }
171}
172
173impl From<DurationString> for String {
174    fn from(value: DurationString) -> Self {
175        let ns = value.0.as_nanos();
176        if ns % YEAR_IN_NANO == 0 {
177            return (ns / YEAR_IN_NANO).to_string() + "y";
178        }
179        if ns % WEEK_IN_NANO == 0 {
180            return (ns / WEEK_IN_NANO).to_string() + "w";
181        }
182        if ns % DAY_IN_NANO == 0 {
183            return (ns / DAY_IN_NANO).to_string() + "d";
184        }
185        if ns % HOUR_IN_NANO == 0 {
186            return (ns / HOUR_IN_NANO).to_string() + "h";
187        }
188        if ns % MINUTE_IN_NANO == 0 {
189            return (ns / MINUTE_IN_NANO).to_string() + "m";
190        }
191        if ns % SECOND_IN_NANO == 0 {
192            return (ns / SECOND_IN_NANO).to_string() + "s";
193        }
194        if ns % MILLISECOND_IN_NANO == 0 {
195            return (ns / MILLISECOND_IN_NANO).to_string() + "ms";
196        }
197        if ns % MICROSECOND_IN_NANO == 0 {
198            return (ns / MICROSECOND_IN_NANO).to_string() + "us";
199        }
200        ns.to_string() + "ns"
201    }
202}
203
204impl From<Duration> for DurationString {
205    fn from(duration: Duration) -> Self {
206        DurationString(duration)
207    }
208}
209
210impl TryFrom<String> for DurationString {
211    type Error = Error;
212
213    fn try_from(duration: String) -> std::result::Result<Self, Self::Error> {
214        duration.parse()
215    }
216}
217
218impl FromStr for DurationString {
219    type Err = Error;
220
221    fn from_str(duration: &str) -> std::result::Result<Self, Self::Err> {
222        let duration: Vec<char> = duration.chars().filter(|c| !c.is_whitespace()).collect();
223        let mut grouped_durations: Vec<(Vec<char>, Vec<char>)> = vec![(vec![], vec![])];
224        for i in 0..duration.len() {
225            // Vector initialised with a starting element so unwraps should never panic
226            if duration[i].is_numeric() {
227                grouped_durations.last_mut().unwrap().0.push(duration[i]);
228            } else {
229                grouped_durations.last_mut().unwrap().1.push(duration[i]);
230            }
231            if i != duration.len() - 1 && !duration[i].is_numeric() && duration[i + 1].is_numeric()
232            {
233                // move to next group
234                grouped_durations.push((vec![], vec![]));
235            }
236        }
237        if grouped_durations.is_empty() {
238            // `duration` either contains no numbers or no letters
239            return Err(Error::Format);
240        }
241        let mut total_duration = Duration::new(0, 0);
242        for (period, format) in grouped_durations {
243            let period = match period.iter().collect::<String>().parse::<u64>() {
244                Ok(period) => Ok(period),
245                Err(err) => Err(Error::ParseInt(err)),
246            }?;
247            let multiply_period = |multiplier: u32| -> std::result::Result<Duration, Self::Err> {
248                Duration::from_secs(period)
249                    .checked_mul(multiplier)
250                    .ok_or(Error::Overflow)
251            };
252            let period_duration = match format.iter().collect::<String>().as_ref() {
253                "ns" => Ok(Duration::from_nanos(period)),
254                "us" => Ok(Duration::from_micros(period)),
255                "ms" => Ok(Duration::from_millis(period)),
256                "s" => Ok(Duration::from_secs(period)),
257                "m" => multiply_period(MINUTE_IN_SECONDS),
258                "h" => multiply_period(HOUR_IN_SECONDS),
259                "d" => multiply_period(DAY_IN_SECONDS),
260                "w" => multiply_period(WEEK_IN_SECONDS),
261                "y" => multiply_period(YEAR_IN_SECONDS),
262                _ => Err(Error::Format),
263            }?;
264            total_duration = total_duration
265                .checked_add(period_duration)
266                .ok_or(Error::Overflow)?;
267        }
268        Ok(DurationString(total_duration))
269    }
270}
271
272impl Deref for DurationString {
273    type Target = Duration;
274
275    fn deref(&self) -> &Self::Target {
276        &self.0
277    }
278}
279
280impl DerefMut for DurationString {
281    fn deref_mut(&mut self) -> &mut Self::Target {
282        &mut self.0
283    }
284}
285
286impl Borrow<Duration> for DurationString {
287    fn borrow(&self) -> &Duration {
288        &self.0
289    }
290}
291
292impl BorrowMut<Duration> for DurationString {
293    fn borrow_mut(&mut self) -> &mut Duration {
294        &mut self.0
295    }
296}
297
298impl PartialEq<Duration> for DurationString {
299    fn eq(&self, other: &Duration) -> bool {
300        self.0.eq(other)
301    }
302}
303
304impl PartialEq<DurationString> for Duration {
305    fn eq(&self, other: &DurationString) -> bool {
306        self.eq(&other.0)
307    }
308}
309
310impl PartialOrd<Duration> for DurationString {
311    fn partial_cmp(&self, other: &Duration) -> Option<std::cmp::Ordering> {
312        self.0.partial_cmp(other)
313    }
314}
315
316impl PartialOrd<DurationString> for Duration {
317    fn partial_cmp(&self, other: &DurationString) -> Option<std::cmp::Ordering> {
318        self.partial_cmp(&other.0)
319    }
320}
321
322impl Add for DurationString {
323    type Output = Self;
324
325    fn add(self, other: Self) -> Self::Output {
326        Self::new(self.0.add(other.0))
327    }
328}
329
330impl Add<Duration> for DurationString {
331    type Output = Self;
332
333    fn add(self, other: Duration) -> Self::Output {
334        Self::new(self.0.add(other))
335    }
336}
337
338impl Add<DurationString> for Duration {
339    type Output = Self;
340
341    fn add(self, other: DurationString) -> Self::Output {
342        self.add(other.0)
343    }
344}
345
346impl AddAssign for DurationString {
347    fn add_assign(&mut self, other: Self) {
348        self.0.add_assign(other.0);
349    }
350}
351
352impl AddAssign<Duration> for DurationString {
353    fn add_assign(&mut self, other: Duration) {
354        self.0.add_assign(other);
355    }
356}
357
358impl AddAssign<DurationString> for Duration {
359    fn add_assign(&mut self, other: DurationString) {
360        self.add_assign(other.0);
361    }
362}
363
364impl Sub for DurationString {
365    type Output = Self;
366
367    fn sub(self, other: Self) -> Self::Output {
368        Self::new(self.0.sub(other.0))
369    }
370}
371
372impl Sub<Duration> for DurationString {
373    type Output = Self;
374
375    fn sub(self, other: Duration) -> Self::Output {
376        Self::new(self.0.sub(other))
377    }
378}
379
380impl Sub<DurationString> for Duration {
381    type Output = Self;
382
383    fn sub(self, other: DurationString) -> Self::Output {
384        self.sub(other.0)
385    }
386}
387
388impl SubAssign for DurationString {
389    fn sub_assign(&mut self, other: Self) {
390        self.0.sub_assign(other.0);
391    }
392}
393
394impl SubAssign<Duration> for DurationString {
395    fn sub_assign(&mut self, other: Duration) {
396        self.0.sub_assign(other);
397    }
398}
399
400impl SubAssign<DurationString> for Duration {
401    fn sub_assign(&mut self, other: DurationString) {
402        self.sub_assign(other.0);
403    }
404}
405
406impl Mul<u32> for DurationString {
407    type Output = Self;
408
409    fn mul(self, other: u32) -> Self::Output {
410        Self::new(self.0.mul(other))
411    }
412}
413
414impl Mul<DurationString> for u32 {
415    type Output = DurationString;
416
417    fn mul(self, other: DurationString) -> Self::Output {
418        DurationString::new(self.mul(other.0))
419    }
420}
421
422impl MulAssign<u32> for DurationString {
423    fn mul_assign(&mut self, other: u32) {
424        self.0.mul_assign(other);
425    }
426}
427
428impl Div<u32> for DurationString {
429    type Output = Self;
430
431    fn div(self, other: u32) -> Self::Output {
432        Self::new(self.0.div(other))
433    }
434}
435
436impl DivAssign<u32> for DurationString {
437    fn div_assign(&mut self, other: u32) {
438        self.0.div_assign(other);
439    }
440}
441
442impl Sum for DurationString {
443    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
444        Self::new(Duration::sum(iter.map(|duration_string| duration_string.0)))
445    }
446}
447
448impl<'a> Sum<&'a DurationString> for DurationString {
449    fn sum<I: Iterator<Item = &'a DurationString>>(iter: I) -> Self {
450        Self::new(Duration::sum(
451            iter.map(|duration_string| &duration_string.0),
452        ))
453    }
454}
455
456#[cfg(feature = "serde")]
457struct DurationStringVisitor {
458    marker: PhantomData<fn() -> DurationString>,
459}
460
461#[cfg(feature = "serde")]
462impl DurationStringVisitor {
463    fn new() -> Self {
464        Self {
465            marker: PhantomData,
466        }
467    }
468}
469
470#[cfg(feature = "serde")]
471#[allow(clippy::needless_lifetimes)]
472impl<'de> serde::de::Visitor<'de> for DurationStringVisitor {
473    type Value = DurationString;
474
475    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
476        formatter.write_str("string")
477    }
478
479    fn visit_str<E>(self, string: &str) -> std::result::Result<Self::Value, E>
480    where
481        E: serde::de::Error,
482    {
483        match DurationString::from_string(string.to_string()) {
484            Ok(d) => Ok(d),
485            Err(s) => Err(serde::de::Error::invalid_value(
486                Unexpected::Str(&s.to_string()),
487                &self,
488            )),
489        }
490    }
491}
492
493#[cfg(feature = "serde")]
494impl<'de> serde::Deserialize<'de> for DurationString {
495    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
496    where
497        D: serde::Deserializer<'de>,
498    {
499        deserializer.deserialize_str(DurationStringVisitor::new())
500    }
501}
502
503#[cfg(feature = "serde")]
504impl serde::Serialize for DurationString {
505    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
506    where
507        S: serde::Serializer,
508    {
509        serializer.serialize_str(&self.to_string())
510    }
511}
512#[cfg(test)]
513mod tests {
514    use super::*;
515    #[cfg(feature = "serde")]
516    use serde::{Deserialize, Serialize};
517
518    #[cfg(feature = "serde")]
519    #[derive(Serialize, Deserialize)]
520    struct SerdeSupport {
521        d: DurationString,
522    }
523
524    #[cfg(feature = "serde")]
525    #[test]
526    fn test_serialize_trait() {
527        let s = SerdeSupport {
528            d: DurationString::from_string(String::from("1m")).unwrap(),
529        };
530        assert_eq!(r#"{"d":"1m"}"#, serde_json::to_string(&s).unwrap());
531    }
532
533    #[cfg(feature = "serde")]
534    #[test]
535    fn test_deserialize_trait() {
536        let s = r#"{"d":"2m"}"#;
537        match serde_json::from_str::<SerdeSupport>(s) {
538            Ok(v) => {
539                assert_eq!(v.d.to_string(), "2m");
540            }
541            Err(err) => panic!("failed to deserialize: {}", err),
542        }
543    }
544
545    #[test]
546    fn test_string_int_overflow() {
547        DurationString::from_string(String::from("ms")).expect_err("parsing \"ms\" should fail");
548    }
549
550    #[test]
551    fn test_from_string_no_char() {
552        DurationString::from_string(String::from("1234"))
553            .expect_err("parsing \"1234\" should fail");
554    }
555
556    // fn test_from_string
557    #[test]
558    fn test_from_string() {
559        let d = DurationString::from_string(String::from("100ms"));
560        assert_eq!("100ms", format!("{}", d.unwrap()));
561    }
562
563    #[test]
564    fn test_display_trait() {
565        let d = DurationString::from(Duration::from_millis(100));
566        assert_eq!("100ms", format!("{d}"));
567    }
568
569    #[test]
570    fn test_from_duration() {
571        let d: String = DurationString::from(Duration::from_millis(100)).into();
572        assert_eq!(d, String::from("100ms"));
573    }
574
575    fn test_parse_string(input_str: &str, expected_duration: Duration) {
576        let d_fromstr: Duration = input_str
577            .parse::<DurationString>()
578            .expect("Parse with FromStr failed")
579            .into();
580        assert_eq!(d_fromstr, expected_duration, "FromStr");
581        let d_using_tryfrom: Duration = DurationString::try_from(input_str.to_owned())
582            .expect("Parse with TryFrom failed")
583            .into();
584        assert_eq!(d_using_tryfrom, expected_duration, "TryFrom");
585    }
586
587    #[test]
588    fn test_from_string_ms() {
589        test_parse_string("100ms", Duration::from_millis(100));
590    }
591
592    #[test]
593    fn test_from_string_us() {
594        test_parse_string("100us", Duration::from_micros(100));
595    }
596
597    #[test]
598    fn test_from_string_us_ms() {
599        test_parse_string("1ms100us", Duration::from_micros(1100));
600    }
601
602    #[test]
603    fn test_from_string_ns() {
604        test_parse_string("100ns", Duration::from_nanos(100));
605    }
606
607    #[test]
608    fn test_from_string_s() {
609        test_parse_string("1s", Duration::from_secs(1));
610    }
611
612    #[test]
613    fn test_from_string_m() {
614        test_parse_string("1m", Duration::from_secs(60));
615    }
616
617    #[test]
618    fn test_from_string_m_s() {
619        test_parse_string("1m 1s", Duration::from_secs(61));
620    }
621
622    #[test]
623    fn test_from_string_h() {
624        test_parse_string("1h", Duration::from_secs(3600));
625    }
626
627    #[test]
628    fn test_from_string_h_m() {
629        test_parse_string("1h30m", Duration::from_secs(5400));
630    }
631
632    #[test]
633    fn test_from_string_h_m2() {
634        test_parse_string("1h128m", Duration::from_secs(11280));
635    }
636
637    #[test]
638    fn test_from_string_d() {
639        test_parse_string("1d", Duration::from_secs(86_400));
640    }
641
642    #[test]
643    fn test_from_string_w() {
644        test_parse_string("1w", Duration::from_secs(604_800));
645    }
646
647    #[test]
648    fn test_from_string_w_s() {
649        test_parse_string("1w 1s", Duration::from_secs(604_801));
650    }
651
652    #[test]
653    fn test_from_string_y() {
654        test_parse_string("1y", Duration::from_secs(31_556_926));
655    }
656
657    #[test]
658    fn test_into_string_ms() {
659        let d: String = DurationString::try_from(String::from("100ms"))
660            .unwrap()
661            .into();
662        assert_eq!(d, "100ms");
663    }
664
665    #[test]
666    fn test_into_string_s() {
667        let d: String = DurationString::try_from(String::from("1s")).unwrap().into();
668        assert_eq!(d, "1s");
669    }
670
671    #[test]
672    fn test_into_string_m() {
673        let d: String = DurationString::try_from(String::from("1m")).unwrap().into();
674        assert_eq!(d, "1m");
675    }
676
677    #[test]
678    fn test_into_string_h() {
679        let d: String = DurationString::try_from(String::from("1h")).unwrap().into();
680        assert_eq!(d, "1h");
681    }
682
683    #[test]
684    fn test_into_string_d() {
685        let d: String = DurationString::try_from(String::from("1d")).unwrap().into();
686        assert_eq!(d, "1d");
687    }
688
689    #[test]
690    fn test_into_string_w() {
691        let d: String = DurationString::try_from(String::from("1w")).unwrap().into();
692        assert_eq!(d, "1w");
693    }
694
695    #[test]
696    fn test_into_string_y() {
697        let d: String = DurationString::try_from(String::from("1y")).unwrap().into();
698        assert_eq!(d, "1y");
699    }
700
701    #[test]
702    fn test_into_string_overflow_unit() {
703        let d: String = DurationString::try_from(String::from("1000ms"))
704            .unwrap()
705            .into();
706        assert_eq!(d, "1s");
707
708        let d: String = DurationString::try_from(String::from("60000ms"))
709            .unwrap()
710            .into();
711        assert_eq!(d, "1m");
712
713        let d: String = DurationString::try_from(String::from("61000ms"))
714            .unwrap()
715            .into();
716        assert_eq!(d, "61s");
717    }
718
719    #[test]
720    fn test_from_string_invalid_string() {
721        DurationString::try_from(String::from("1000x"))
722            .expect_err("Should have failed with invalid format");
723    }
724
725    #[test]
726    fn test_try_from_string_overflow_y() {
727        let result = DurationString::try_from(String::from("584554530873y"));
728        assert_eq!(result, Err(Error::Overflow));
729    }
730
731    #[test]
732    fn test_try_from_string_overflow_y_w() {
733        let result = DurationString::try_from(String::from("584554530872y 29w"));
734        assert_eq!(result, Err(Error::Overflow));
735    }
736
737    #[test]
738    fn test_eq() {
739        let duration = Duration::from_secs(1);
740        assert_eq!(DurationString::new(duration), DurationString::new(duration));
741        assert_eq!(DurationString::new(duration), duration);
742        assert_eq!(duration, DurationString::new(duration));
743    }
744
745    #[test]
746    fn test_ne() {
747        let a = Duration::from_secs(1);
748        let b = Duration::from_secs(2);
749        assert_ne!(DurationString::new(a), DurationString::new(b));
750        assert_ne!(DurationString::new(a), b);
751        assert_ne!(a, DurationString::new(b));
752    }
753
754    #[test]
755    fn test_lt() {
756        let a = Duration::from_secs(1);
757        let b = Duration::from_secs(2);
758        assert!(DurationString::new(a) < DurationString::new(b));
759        assert!(DurationString::new(a) < b);
760        assert!(a < DurationString::new(b));
761    }
762
763    #[test]
764    fn test_le() {
765        let a = Duration::from_secs(1);
766        let b = Duration::from_secs(2);
767        assert!(DurationString::new(a) <= DurationString::new(b));
768        assert!(DurationString::new(a) <= b);
769        assert!(a <= DurationString::new(b));
770        let a = Duration::from_secs(1);
771        let b = Duration::from_secs(1);
772        assert!(DurationString::new(a) <= DurationString::new(b));
773        assert!(DurationString::new(a) <= b);
774        assert!(a <= DurationString::new(b));
775    }
776
777    #[test]
778    fn test_gt() {
779        let a = Duration::from_secs(2);
780        let b = Duration::from_secs(1);
781        assert!(DurationString::new(a) > DurationString::new(b));
782        assert!(DurationString::new(a) > b);
783        assert!(a > DurationString::new(b));
784    }
785
786    #[test]
787    fn test_ge() {
788        let a = Duration::from_secs(2);
789        let b = Duration::from_secs(1);
790        assert!(DurationString::new(a) >= DurationString::new(b));
791        assert!(DurationString::new(a) >= b);
792        assert!(a >= DurationString::new(b));
793        let a = Duration::from_secs(1);
794        let b = Duration::from_secs(1);
795        assert!(DurationString::new(a) >= DurationString::new(b));
796        assert!(DurationString::new(a) >= b);
797        assert!(a >= DurationString::new(b));
798    }
799
800    #[test]
801    fn test_add() {
802        let a = Duration::from_secs(1);
803        let b = Duration::from_secs(1);
804        let result = a + b;
805        assert_eq!(
806            DurationString::new(a) + DurationString::new(b),
807            DurationString::new(result)
808        );
809        assert_eq!(DurationString::new(a) + b, DurationString::new(result));
810        assert_eq!(a + DurationString::new(b), result);
811    }
812
813    #[test]
814    fn test_add_assign() {
815        let a = Duration::from_secs(1);
816        let b = Duration::from_secs(1);
817        let result = a + b;
818        let mut duration_string_duration_string = DurationString::new(a);
819        duration_string_duration_string += DurationString::new(b);
820        let mut duration_string_duration = DurationString::new(a);
821        duration_string_duration += b;
822        let mut duration_duration_string = a;
823        duration_duration_string += DurationString::new(b);
824        assert_eq!(duration_string_duration_string, DurationString::new(result));
825        assert_eq!(duration_string_duration, DurationString::new(result));
826        assert_eq!(duration_duration_string, result);
827    }
828
829    #[test]
830    fn test_sub() {
831        let a = Duration::from_secs(1);
832        let b = Duration::from_secs(1);
833        let result = a - b;
834        assert_eq!(
835            DurationString::new(a) - DurationString::new(b),
836            DurationString::new(result)
837        );
838        assert_eq!(DurationString::new(a) - b, DurationString::new(result));
839        assert_eq!(a - DurationString::new(b), result);
840    }
841
842    #[test]
843    fn test_sub_assign() {
844        let a = Duration::from_secs(1);
845        let b = Duration::from_secs(1);
846        let result = a - b;
847        let mut duration_string_duration_string = DurationString::new(a);
848        duration_string_duration_string -= DurationString::new(b);
849        let mut duration_string_duration = DurationString::new(a);
850        duration_string_duration -= b;
851        let mut duration_duration_string = a;
852        duration_duration_string -= DurationString::new(b);
853        assert_eq!(duration_string_duration_string, DurationString::new(result));
854        assert_eq!(duration_string_duration, DurationString::new(result));
855        assert_eq!(duration_duration_string, result);
856    }
857
858    #[test]
859    fn test_mul() {
860        let a = 2u32;
861        let a_duration = DurationString::new(Duration::from_secs(a.into()));
862        let b = 4u32;
863        let b_duration = DurationString::new(Duration::from_secs(b.into()));
864        let result = DurationString::new(Duration::from_secs((a * b).into()));
865        assert_eq!(a_duration * b, result);
866        assert_eq!(a * b_duration, result);
867    }
868
869    #[test]
870    fn test_mul_assign() {
871        let a = 2u32;
872        let b = 4u32;
873        let result = DurationString::new(Duration::from_secs((a * b).into()));
874        let mut duration_string_u32 = DurationString::new(Duration::from_secs(a.into()));
875        duration_string_u32 *= b;
876        assert_eq!(duration_string_u32, result);
877    }
878
879    #[test]
880    fn test_div() {
881        let a = 8u32;
882        let a_duration = DurationString::new(Duration::from_secs(a.into()));
883        let b = 4u32;
884        let result = DurationString::new(Duration::from_secs((a / b).into()));
885        assert_eq!(a_duration / b, result);
886    }
887
888    #[test]
889    fn test_div_assign() {
890        let a = 8u32;
891        let b = 4u32;
892        let result = DurationString::new(Duration::from_secs((a / b).into()));
893        let mut duration_string_u32 = DurationString::new(Duration::from_secs(a.into()));
894        duration_string_u32 /= b;
895        assert_eq!(duration_string_u32, result);
896    }
897
898    #[test]
899    fn test_sum() {
900        let durations = [
901            Duration::from_secs(1),
902            Duration::from_secs(2),
903            Duration::from_secs(3),
904            Duration::from_secs(4),
905            Duration::from_secs(5),
906            Duration::from_secs(6),
907            Duration::from_secs(7),
908            Duration::from_secs(8),
909            Duration::from_secs(9),
910        ];
911        let result = DurationString::new(durations.iter().sum());
912        let durations = durations
913            .iter()
914            .map(|duration| Into::<DurationString>::into(*duration))
915            .collect::<Vec<_>>();
916        assert_eq!(durations.iter().sum::<DurationString>(), result);
917        assert_eq!(durations.into_iter().sum::<DurationString>(), result);
918    }
919}