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