gametime/
span.rs

1//! This module contains `TimeSpan` type that represents durations.
2//! The name `Duration` is not used to avoid confusion with std type.
3//!
4//!
5//! Contains traits and functions to work with `TimeSpan`s.
6//!
7
8use core::{
9    fmt::{self, Debug, Display},
10    ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Range, Rem, RemAssign, Sub, SubAssign},
11    str::FromStr,
12    time::Duration,
13};
14
15const MAX_TIME_SPAN_STRING: usize = 48;
16
17/// An interval in between time stamps.
18/// This type is used to represent durations with nanosecond precision.
19#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
20#[repr(transparent)]
21pub struct TimeSpan {
22    nanos: i64,
23}
24
25impl TimeSpan {
26    fn fmt(self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27        if self == Self::ZERO {
28            f.write_str("0")
29        } else {
30            if self.is_negative() {
31                f.write_str("-")?;
32            }
33            let mut span = self.abs();
34
35            let days = span / Self::DAY;
36            span %= Self::DAY;
37
38            let hours = span / Self::HOUR;
39            span %= Self::HOUR;
40
41            let minutes = span / Self::MINUTE;
42            span %= Self::MINUTE;
43
44            let seconds = span / Self::SECOND;
45            span %= Self::SECOND;
46
47            let millis = span / Self::MILLISECOND;
48            span %= Self::MILLISECOND;
49
50            let micros = span / Self::MICROSECOND;
51            span %= Self::MICROSECOND;
52
53            let nanos = span / Self::NANOSECOND;
54
55            if days > 0 || hours > 0 || minutes > 0 {
56                if days > 0 {
57                    write!(f, "{days}d")?;
58                }
59
60                if days > 0 {
61                    write!(f, "{hours:02}:")?;
62                } else if hours > 0 {
63                    write!(f, "{hours}:")?;
64                }
65
66                if days > 0 || hours > 0 {
67                    write!(f, "{minutes:02}")?;
68                } else if minutes > 0 {
69                    write!(f, "{minutes}")?;
70                }
71
72                if nanos > 0 {
73                    write!(f, ":{seconds:02}.{millis:03}{micros:03}{nanos:03}")
74                } else if micros > 0 {
75                    write!(f, ":{seconds:02}.{millis:03}{micros:03}")
76                } else if millis > 0 {
77                    write!(f, ":{seconds:02}.{millis:03}")
78                } else if seconds > 0 || days == 0 {
79                    write!(f, ":{seconds:02}")
80                } else {
81                    Ok(())
82                }
83            } else if seconds > 0 {
84                if nanos > 0 {
85                    write!(f, "{seconds}.{millis:03}{micros:03}{nanos:03}s")
86                } else if micros > 0 {
87                    write!(f, "{seconds}.{millis:03}{micros:03}s")
88                } else if millis > 0 {
89                    write!(f, "{seconds}.{millis:03}s")
90                } else {
91                    write!(f, "{seconds}s")
92                }
93            } else if millis > 0 {
94                if nanos > 0 {
95                    write!(f, "{millis}.{micros:03}{nanos:03}ms")
96                } else if micros > 0 {
97                    write!(f, "{millis}.{micros:03}ms")
98                } else {
99                    write!(f, "{millis}ms")
100                }
101            } else if micros > 0 {
102                if nanos > 0 {
103                    write!(f, "{micros}.{nanos:03}us")
104                } else {
105                    write!(f, "{micros}us")
106                }
107            } else {
108                write!(f, "{nanos}ns")
109            }
110        }
111    }
112
113    fn fmt_full(self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114        if self.is_negative() {
115            f.write_str("-")?;
116        }
117        let mut span = self.abs();
118        let days = span / Self::DAY;
119        span %= Self::DAY;
120        let hours = span / Self::HOUR;
121        span %= Self::HOUR;
122        let minutes = span / Self::MINUTE;
123        span %= Self::MINUTE;
124        let seconds = span / Self::SECOND;
125        span %= Self::SECOND;
126        let nanos = span / Self::NANOSECOND;
127
128        write!(
129            f,
130            "{days:01}d{hours:02}:{minutes:02}:{seconds:02}.{nanos:09}"
131        )
132    }
133
134    fn fmt_nanos(self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        write!(f, "{}ns", self.nanos)
136    }
137
138    /// Longest possible time span in displayed format is
139    /// `-106751d23:59:59.999999999`
140    const MAX_DISPLAY_LENGTH: usize = 26;
141
142    /// Buffer enough to hold any time span in displayed format.
143    pub const DISPLAY_BUFFER: [u8; Self::MAX_DISPLAY_LENGTH] = [0; Self::MAX_DISPLAY_LENGTH];
144
145    /// Formats this time span into a buffer.
146    /// Returns a string slice that contains the formatted time span.
147    ///
148    /// `&mut Self::DISPLAY_BUFFER` can be used as second argument.
149    pub fn display_to_buffer(self, buf: &mut [u8; Self::MAX_DISPLAY_LENGTH]) -> &mut str {
150        #![allow(clippy::missing_panics_doc)] // False positive. Panics is not possible here.
151
152        use fmt::Write;
153
154        struct Buffer<'a> {
155            buf: &'a mut [u8; TimeSpan::MAX_DISPLAY_LENGTH],
156            offset: usize,
157        }
158
159        impl<'a> Buffer<'a> {
160            fn new(buf: &'a mut [u8; TimeSpan::MAX_DISPLAY_LENGTH]) -> Self {
161                Buffer { buf, offset: 0 }
162            }
163
164            fn into_str(self) -> &'a mut str {
165                str::from_utf8_mut(&mut self.buf[..self.offset])
166                    .expect("Valid UTF-8 written to buffer")
167            }
168        }
169
170        impl<'a> Write for Buffer<'a> {
171            fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
172                if s.len() > self.buf.len() - self.offset {
173                    return Err(fmt::Error);
174                }
175
176                self.buf[self.offset..][..s.len()].copy_from_slice(s.as_bytes());
177                self.offset += s.len();
178                Ok(())
179            }
180        }
181
182        let mut buffer = Buffer::new(buf);
183
184        match write!(&mut buffer, "{self}") {
185            Ok(()) => buffer.into_str(),
186            Err(_) => unreachable!("Buffer is large enough to hold any time span"),
187        }
188    }
189}
190
191impl Debug for TimeSpan {
192    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193        if f.alternate() {
194            self.fmt_nanos(f)
195        } else {
196            Self::fmt(*self, f)
197        }
198    }
199}
200
201impl Display for TimeSpan {
202    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203        if f.alternate() {
204            self.fmt_full(f)
205        } else {
206            Self::fmt(*self, f)
207        }
208    }
209}
210
211#[derive(Debug)]
212pub enum TimeSpanParseErr {
213    NonASCII,
214    StringTooLarge { len: usize },
215    IntParseError { source: core::num::ParseIntError },
216    UnexpectedDelimiter { delim: char, pos: usize },
217    UnexpectedEndOfString,
218    UnexpectedSuffix,
219    HoursOutOfBound { hours: i64 },
220    MinutesOutOfBound { minutes: i64 },
221    SecondsOutOfBound { seconds: i64 },
222}
223
224impl fmt::Display for TimeSpanParseErr {
225    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226        match self {
227            Self::NonASCII => f.write_str("Time spans encoded in strings are always ASCII"),
228            Self::StringTooLarge { len } => {
229                write!(
230                    f,
231                    "Valid time span string may never exceed {MAX_TIME_SPAN_STRING} bytes. String is {len}"
232                )
233            }
234            Self::IntParseError { .. } => f.write_str("Failed to parse integer"),
235            Self::UnexpectedDelimiter { delim, pos } => {
236                write!(f, "Unexpected delimiter '{delim}' at {pos}")
237            }
238            Self::UnexpectedEndOfString => f.write_str("Unexpected end of string"),
239            Self::UnexpectedSuffix => {
240                f.write_str("Unexpected suffix. Only `s`, `ms` and `us` suffixes are supported")
241            }
242            Self::HoursOutOfBound { hours } => {
243                write!(f, "Hours must be in range 0-23 when days are specified. Value at hours position is '{hours}'")
244            }
245            Self::MinutesOutOfBound { minutes } => {
246                write!(f, "Minutes must be in range 0-59 when hours are specified. Value at minutes position is '{minutes}'")
247            }
248            Self::SecondsOutOfBound { seconds } => {
249                write!(
250                    f,
251                    "Seconds must be in range 0-59 when minutes are specified. Value at seconds position is '{seconds}'"
252                )
253            }
254        }
255    }
256}
257
258#[cfg(feature = "std")]
259impl std::error::Error for TimeSpanParseErr {
260    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
261        match self {
262            Self::IntParseError { source } => Some(source),
263            _ => None,
264        }
265    }
266}
267
268struct Ranges {
269    days: Option<Range<usize>>,
270    hours: Option<Range<usize>>,
271    minutes: Option<Range<usize>>,
272    seconds: Option<Range<usize>>,
273    fract: Option<Range<usize>>,
274    denom: u32,
275}
276
277impl Ranges {
278    fn parse(self, s: &str) -> Result<TimeSpan, TimeSpanParseErr> {
279        let seconds: i64 = self
280            .seconds
281            .map_or(Ok(0), |r| s[r].trim().parse())
282            .map_err(|source| TimeSpanParseErr::IntParseError { source })?;
283
284        if self.minutes.is_some() && seconds > 59 {
285            return Err(TimeSpanParseErr::SecondsOutOfBound { seconds });
286        }
287
288        let minutes: i64 = self
289            .minutes
290            .map_or(Ok(0), |r| s[r].trim().parse())
291            .map_err(|source| TimeSpanParseErr::IntParseError { source })?;
292
293        if self.hours.is_some() && minutes > 59 {
294            return Err(TimeSpanParseErr::MinutesOutOfBound { minutes });
295        }
296
297        let hours: i64 = self
298            .hours
299            .map_or(Ok(0), |r| s[r].trim().parse())
300            .map_err(|source| TimeSpanParseErr::IntParseError { source })?;
301
302        if self.days.is_some() && hours > 23 {
303            return Err(TimeSpanParseErr::HoursOutOfBound { hours });
304        }
305
306        let days: i64 = self
307            .days
308            .map_or(Ok(0), |r| s[r].trim().parse())
309            .map_err(|source| TimeSpanParseErr::IntParseError { source })?;
310
311        let fract: i64 = self
312            .fract
313            .map_or(Ok(0), |r| s[r].trim().parse())
314            .map_err(|source| TimeSpanParseErr::IntParseError { source })?;
315
316        let micros = match self.denom {
317            denom @ 0..6 => fract * 10i64.pow(6 - denom),
318            6 => fract,
319            denom @ 7.. => fract / 10i64.pow(denom - 6),
320        };
321
322        Ok(days * TimeSpan::DAY
323            + hours * TimeSpan::HOUR
324            + minutes * TimeSpan::MINUTE
325            + seconds * TimeSpan::SECOND
326            + micros * TimeSpan::MICROSECOND)
327    }
328}
329
330impl FromStr for TimeSpan {
331    type Err = TimeSpanParseErr;
332
333    #[allow(clippy::too_many_lines)]
334    fn from_str(s: &str) -> Result<Self, Self::Err> {
335        #![allow(clippy::cast_possible_truncation)]
336
337        if !s.is_ascii() {
338            return Err(TimeSpanParseErr::NonASCII);
339        }
340
341        if s.len() > MAX_TIME_SPAN_STRING {
342            return Err(TimeSpanParseErr::StringTooLarge { len: s.len() });
343        }
344
345        let mut separators =
346            s.match_indices(|c: char| !c.is_ascii_digit() && !c.is_ascii_whitespace());
347
348        match separators.next() {
349            Some((dh, "d" | "D" | "t" | "T")) => match separators.next() {
350                Some((hm, ":")) => match separators.next() {
351                    None => Ranges {
352                        days: Some(0..dh),
353                        hours: Some(dh + 1..hm),
354                        minutes: Some(hm + 1..s.len()),
355                        seconds: None,
356                        fract: None,
357                        denom: 0,
358                    },
359                    Some((ms, ":")) => match separators.next() {
360                        None => Ranges {
361                            days: Some(0..dh),
362                            hours: Some(dh + 1..hm),
363                            minutes: Some(hm + 1..ms),
364                            seconds: Some(ms + 1..s.len()),
365                            fract: None,
366                            denom: 0,
367                        },
368                        Some((sf, ".")) => {
369                            if let Some((pos, delim)) = separators.next() {
370                                return Err(TimeSpanParseErr::UnexpectedDelimiter {
371                                    delim: delim.chars().next().unwrap(),
372                                    pos,
373                                });
374                            }
375                            Ranges {
376                                days: Some(0..dh),
377                                hours: Some(dh + 1..hm),
378                                minutes: Some(hm + 1..ms),
379                                seconds: Some(ms + 1..sf),
380                                fract: Some(sf + 1..s.len().min(sf + 21)),
381                                denom: (s.len() - sf - 1).min(20) as u32,
382                            }
383                        }
384
385                        Some((pos, delim)) => {
386                            return Err(TimeSpanParseErr::UnexpectedDelimiter {
387                                delim: delim.chars().next().unwrap(),
388                                pos,
389                            });
390                        }
391                    },
392                    Some((pos, delim)) => {
393                        return Err(TimeSpanParseErr::UnexpectedDelimiter {
394                            delim: delim.chars().next().unwrap(),
395                            pos,
396                        });
397                    }
398                },
399                Some((pos, delim)) => {
400                    return Err(TimeSpanParseErr::UnexpectedDelimiter {
401                        delim: delim.chars().next().unwrap(),
402                        pos,
403                    });
404                }
405                None => {
406                    return Err(TimeSpanParseErr::UnexpectedEndOfString);
407                }
408            },
409            Some((hms, ":")) => match separators.next() {
410                Some((ms, ":")) => match separators.next() {
411                    Some((sf, ".")) => {
412                        if let Some((pos, delim)) = separators.next() {
413                            return Err(TimeSpanParseErr::UnexpectedDelimiter {
414                                delim: delim.chars().next().unwrap(),
415                                pos,
416                            });
417                        }
418                        Ranges {
419                            days: None,
420                            hours: Some(0..hms),
421                            minutes: Some(hms + 1..ms),
422                            seconds: Some(ms + 1..sf),
423                            fract: Some(sf + 1..s.len().min(sf + 21)),
424                            denom: (s.len() - sf - 1).min(20) as u32,
425                        }
426                    }
427                    None => Ranges {
428                        days: None,
429                        hours: Some(0..hms),
430                        minutes: Some(hms + 1..ms),
431                        seconds: Some(ms + 1..s.len()),
432                        fract: None,
433                        denom: 0,
434                    },
435                    Some((pos, delim)) => {
436                        return Err(TimeSpanParseErr::UnexpectedDelimiter {
437                            delim: delim.chars().next().unwrap(),
438                            pos,
439                        });
440                    }
441                },
442                Some((sf, ".")) => {
443                    if let Some((pos, delim)) = separators.next() {
444                        return Err(TimeSpanParseErr::UnexpectedDelimiter {
445                            delim: delim.chars().next().unwrap(),
446                            pos,
447                        });
448                    }
449                    Ranges {
450                        days: None,
451                        hours: None,
452                        minutes: Some(0..hms),
453                        seconds: Some(hms + 1..sf),
454                        fract: Some(sf + 1..s.len()),
455                        denom: (s.len() - sf - 1).min(20) as u32,
456                    }
457                }
458                None => Ranges {
459                    days: None,
460                    hours: None,
461                    minutes: Some(0..hms),
462                    seconds: Some(hms + 1..s.len()),
463                    fract: None,
464                    denom: 0,
465                },
466                Some((pos, delim)) => {
467                    return Err(TimeSpanParseErr::UnexpectedDelimiter {
468                        delim: delim.chars().next().unwrap(),
469                        pos,
470                    });
471                }
472            },
473
474            Some((sf, ".")) => {
475                if let Some((pos, delim)) = separators.next() {
476                    return Err(TimeSpanParseErr::UnexpectedDelimiter {
477                        delim: delim.chars().next().unwrap(),
478                        pos,
479                    });
480                }
481                Ranges {
482                    days: None,
483                    hours: None,
484                    minutes: None,
485                    seconds: Some(0..sf),
486                    fract: Some(sf + 1..s.len()),
487                    denom: (s.len() - sf - 1).min(20) as u32,
488                }
489            }
490
491            Some((suffix, "s")) => {
492                if s[suffix..].trim() != "s" {
493                    return Err(TimeSpanParseErr::UnexpectedSuffix);
494                }
495
496                let seconds: i64 = s[..suffix]
497                    .trim()
498                    .parse()
499                    .map_err(|source| TimeSpanParseErr::IntParseError { source })?;
500                return Ok(seconds * Self::SECOND);
501            }
502
503            Some((suffix, "m")) => {
504                if s[suffix..].trim() != "ms" {
505                    return Err(TimeSpanParseErr::UnexpectedSuffix);
506                }
507
508                let millis: i64 = s[..suffix]
509                    .trim()
510                    .parse()
511                    .map_err(|source| TimeSpanParseErr::IntParseError { source })?;
512                return Ok(millis * Self::MILLISECOND);
513            }
514
515            Some((suffix, "u")) => {
516                if s[suffix..].trim() != "us" {
517                    return Err(TimeSpanParseErr::UnexpectedSuffix);
518                }
519
520                let micros: i64 = s[..suffix]
521                    .trim()
522                    .parse()
523                    .map_err(|source| TimeSpanParseErr::IntParseError { source })?;
524                return Ok(micros * Self::MICROSECOND);
525            }
526
527            None => {
528                let seconds: i64 = s
529                    .trim()
530                    .parse()
531                    .map_err(|source| TimeSpanParseErr::IntParseError { source })?;
532                return Ok(seconds * Self::SECOND);
533            }
534
535            Some((pos, delim)) => {
536                return Err(TimeSpanParseErr::UnexpectedDelimiter {
537                    delim: delim.chars().next().unwrap(),
538                    pos,
539                });
540            }
541        }
542        .parse(s)
543    }
544}
545
546#[cfg(feature = "serde")]
547impl serde::Serialize for TimeSpan {
548    #[inline]
549    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
550    where
551        S: serde::Serializer,
552    {
553        // Serialize in pretty format for human readable serializer
554        if serializer.is_human_readable() {
555            let mut buf = Self::DISPLAY_BUFFER;
556            let s = self.display_to_buffer(&mut buf);
557
558            serializer.serialize_str(s)
559        } else {
560            serializer.serialize_i64(self.nanos)
561        }
562    }
563}
564
565#[cfg(feature = "serde")]
566impl<'de> serde::Deserialize<'de> for TimeSpan {
567    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
568    where
569        D: serde::Deserializer<'de>,
570    {
571        struct Visitor;
572
573        impl<'de> serde::de::Visitor<'de> for Visitor {
574            type Value = TimeSpan;
575
576            fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
577                fmt.write_str("String with encoded time span or integer representing nanoseconds")
578            }
579
580            fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
581            where
582                E: serde::de::Error,
583            {
584                if v > i64::MAX as u64 {
585                    return Err(E::custom("Time span is too large to fit into i64"));
586                }
587
588                Ok(TimeSpan { nanos: v as i64 })
589            }
590
591            fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
592            where
593                E: serde::de::Error,
594            {
595                Ok(TimeSpan { nanos: v })
596            }
597
598            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
599            where
600                E: serde::de::Error,
601            {
602                v.parse().map_err(|err| E::custom(err))
603            }
604        }
605
606        if deserializer.is_human_readable() {
607            deserializer.deserialize_str(Visitor)
608        } else {
609            deserializer.deserialize_u64(Visitor)
610        }
611    }
612}
613
614impl TimeSpan {
615    /// Converts [`Duration`] into [`TimeSpan`].
616    ///
617    /// # Panics
618    ///
619    /// Panics if the duration is out of bounds for `TimeSpan`.
620    /// Which is longer than `i64::MAX` nanoseconds.
621    #[inline]
622    #[must_use]
623    pub fn from_duration(duration: Duration) -> Self {
624        let nanos = duration.as_nanos();
625        TimeSpan {
626            nanos: nanos
627                .try_into()
628                .expect("Duration is out of bounds for TimeSpan"),
629        }
630    }
631}
632
633impl TimeSpan {
634    /// Converts [`TimeSpan`] into [`Duration`].
635    ///
636    /// # Panics
637    ///
638    /// Panics if the time span is negative.
639    #[inline]
640    #[must_use]
641    pub fn into_duration(self) -> Duration {
642        assert!(
643            !self.is_negative(),
644            "Cannot convert negative TimeSpan into Duration"
645        );
646
647        Duration::new(
648            #[allow(clippy::cast_sign_loss)]
649            {
650                self.as_seconds() as u64
651            },
652            #[allow(clippy::cast_sign_loss)]
653            {
654                (self.as_nanos() % 1_000_000_000) as u32
655            },
656        )
657    }
658}
659
660impl TimeSpan {
661    /// Zero time span.
662    ///
663    /// Represents duration between equal time points.
664    pub const ZERO: Self = TimeSpan { nanos: 0 };
665
666    /// Minimal possible time span.
667    pub const MIN: Self = TimeSpan {
668        // Use negative i64::MAX to represent minimal possible time span.
669        // Which is equal to i64::MIN + 1.
670        // This is to avoid overflow when negating or taking absolute value.
671        nanos: -i64::MAX,
672    };
673
674    /// Maximal possible time span.
675    pub const MAX: Self = TimeSpan {
676        // Use i64::MAX to represent maximal possible time span.
677        nanos: i64::MAX,
678    };
679
680    /// One nanosecond span.
681    /// Minimal possible time span supported by this type.
682    pub const NANOSECOND: Self = TimeSpan { nanos: 1 };
683
684    /// One microsecond span.
685    pub const MICROSECOND: Self = TimeSpan { nanos: 1_000 };
686
687    /// One millisecond span.
688    pub const MILLISECOND: Self = TimeSpan { nanos: 1_000_000 };
689
690    /// One second span.
691    pub const SECOND: Self = TimeSpan {
692        nanos: 1_000_000_000,
693    };
694
695    /// One minute span.
696    pub const MINUTE: Self = TimeSpan {
697        nanos: 60_000_000_000,
698    };
699
700    /// One hour span.
701    pub const HOUR: Self = TimeSpan {
702        nanos: 3_600_000_000_000,
703    };
704
705    /// One day span.
706    pub const DAY: Self = TimeSpan {
707        nanos: 86_400_000_000_000,
708    };
709
710    /// One week.
711    /// Defined as 7 days.
712    pub const WEEK: Self = TimeSpan {
713        nanos: 604_800_000_000_000,
714    };
715
716    /// One Julian year.
717    /// Average year length in Julian calendar.
718    /// Defined as 365.25 days.
719    pub const JULIAN_YEAR: Self = TimeSpan {
720        nanos: 31_557_600_000_000_000,
721    };
722
723    /// One Gregorian year.
724    /// Average year length in Gregorian calendar.
725    /// Defined as 365.2425 days.
726    pub const GREGORIAN_YEAR: Self = TimeSpan {
727        nanos: 31_556_952_000_000,
728    };
729
730    /// One solar year (tropical year).
731    /// Defined as 365.24219 days.
732    pub const SOLAR_YEAR: Self = TimeSpan {
733        nanos: 31_556_925_216_000_000,
734    };
735
736    /// One year.
737    /// Closest value to the average length of a year on Earth.
738    pub const YEAR: Self = Self::GREGORIAN_YEAR;
739
740    /// Constructs time span from number of nanoseconds.
741    #[inline]
742    #[must_use]
743    pub const fn new(nanos: i64) -> TimeSpan {
744        TimeSpan { nanos }
745    }
746
747    /// Returns number of nanoseconds in this time span.
748    #[inline]
749    #[must_use]
750    pub const fn as_nanos(self) -> i64 {
751        self.nanos
752    }
753
754    /// Returns number of microseconds this value represents.
755    #[inline]
756    #[must_use]
757    pub const fn as_micros(self) -> i64 {
758        self.nanos / Self::MICROSECOND.nanos
759    }
760
761    /// Returns number of whole milliseconds this value represents.
762    #[inline]
763    #[must_use]
764    pub const fn as_millis(self) -> i64 {
765        self.nanos / Self::MILLISECOND.nanos
766    }
767
768    /// Returns number of whole seconds this value represents.
769    #[inline]
770    #[must_use]
771    pub const fn as_seconds(self) -> i64 {
772        self.nanos / Self::SECOND.nanos
773    }
774
775    /// Returns number of whole minutes this value represents.
776    #[inline]
777    #[must_use]
778    pub const fn as_minutes(self) -> i64 {
779        self.nanos / Self::MINUTE.nanos
780    }
781
782    /// Returns number of whole hours this value represents.
783    #[inline]
784    #[must_use]
785    pub const fn as_hours(self) -> i64 {
786        self.nanos / Self::HOUR.nanos
787    }
788
789    /// Returns number of whole days this value represents.
790    #[inline]
791    #[must_use]
792    pub const fn as_days(self) -> i64 {
793        self.nanos / Self::DAY.nanos
794    }
795
796    /// Returns number of whole weeks this value represents.
797    #[inline]
798    #[must_use]
799    pub const fn as_weeks(self) -> i64 {
800        self.nanos / Self::WEEK.nanos
801    }
802
803    /// Returns number of seconds as floating point value.
804    /// This function should be used for small-ish spans when high precision is not required.
805    #[inline]
806    #[must_use]
807    pub fn as_secs_f32(self) -> f32 {
808        #![allow(clippy::cast_precision_loss)]
809
810        self.nanos as f32 / Self::SECOND.nanos as f32
811    }
812
813    /// Returns number of seconds as high precision floating point value.
814    #[inline]
815    #[must_use]
816    pub fn as_secs_f64(self) -> f64 {
817        #![allow(clippy::cast_precision_loss)]
818
819        self.nanos as f64 / Self::SECOND.nanos as f64
820    }
821
822    /// Returns absolute value of this time span.
823    #[inline]
824    #[must_use]
825    pub fn abs(self) -> TimeSpan {
826        TimeSpan {
827            nanos: self.nanos.abs(),
828        }
829    }
830
831    /// Returns true if this time span is negative.
832    #[inline]
833    #[must_use]
834    pub fn is_negative(self) -> bool {
835        self.nanos < 0
836    }
837
838    /// Returns sum of two time spans unless it overflows.
839    #[inline]
840    #[must_use]
841    pub const fn checked_add(self, span: TimeSpan) -> Option<TimeSpan> {
842        match self.nanos.checked_add(span.nanos) {
843            None => None,
844            Some(nanos) => Some(TimeSpan { nanos }),
845        }
846    }
847
848    /// Returns checked difference of two time spans unless it overflows.
849    #[inline]
850    #[must_use]
851    pub const fn checked_sub(self, span: TimeSpan) -> Option<TimeSpan> {
852        match self.nanos.checked_sub(span.nanos) {
853            None => None,
854            Some(nanos) => Some(TimeSpan { nanos }),
855        }
856    }
857
858    /// Returns difference of two time spans unless it overflows.
859    #[inline]
860    #[must_use]
861    pub const fn checked_mul(self, value: i64) -> Option<TimeSpan> {
862        match self.nanos.checked_mul(value) {
863            None => None,
864            Some(nanos) => Some(TimeSpan { nanos }),
865        }
866    }
867
868    /// Returns quotient of time span and a scalar value unless it overflows or denominator is zero.
869    #[inline]
870    #[must_use]
871    pub const fn checked_div(self, value: i64) -> Option<TimeSpan> {
872        match self.nanos.checked_div(value) {
873            None => None,
874            Some(nanos) => Some(TimeSpan { nanos }),
875        }
876    }
877
878    /// Returns quotient of time span and a scalar value unless it overflows or denominator is zero.
879    #[inline]
880    #[must_use]
881    pub const fn checked_div_span(self, span: TimeSpan) -> Option<i64> {
882        match self.nanos.checked_div(span.nanos) {
883            None => None,
884            Some(value) => Some(value),
885        }
886    }
887
888    /// Returns quotient of time span divided by a non-zero time span.
889    #[inline]
890    #[must_use]
891    pub const fn div_span(self, span: TimeSpan) -> i64 {
892        self.nanos / span.nanos
893    }
894
895    /// Returns remainder of time span divided by a scalar value unless it overflows or denominator is zero.
896    #[inline]
897    #[must_use]
898    pub const fn checked_rem(self, value: i64) -> Option<TimeSpan> {
899        match self.nanos.checked_rem(value) {
900            None => None,
901            Some(nanos) => Some(TimeSpan { nanos }),
902        }
903    }
904
905    /// Returns remainder of time span divided by a scalar value.
906    #[inline]
907    #[must_use]
908    pub const fn rem(self, value: i64) -> TimeSpan {
909        let nanos = self.nanos % value;
910        TimeSpan { nanos }
911    }
912
913    /// Returns remainder of time span divided by a non-zero time span unless it overflows or denominator is zero.
914    #[inline]
915    #[must_use]
916    pub const fn checked_rem_span(self, span: TimeSpan) -> Option<TimeSpan> {
917        match self.nanos.checked_rem(span.nanos) {
918            None => None,
919            Some(nanos) => Some(TimeSpan { nanos }),
920        }
921    }
922
923    /// Returns remainder of time span divided by a non-zero time span.
924    #[inline]
925    #[must_use]
926    pub const fn rem_span(self, span: TimeSpan) -> TimeSpan {
927        let nanos = self.nanos % span.nanos;
928        TimeSpan { nanos }
929    }
930
931    /// Returns time span from hours, minutes and seconds.
932    #[inline]
933    #[must_use]
934    pub const fn hms(hours: i64, minutes: i64, seconds: i64) -> TimeSpan {
935        TimeSpan {
936            nanos: hours * Self::HOUR.nanos
937                + minutes * Self::MINUTE.nanos
938                + seconds * Self::SECOND.nanos,
939        }
940    }
941
942    /// Returns time span from days, hours, minutes and seconds.
943    #[inline]
944    #[must_use]
945    pub const fn dhms(days: i64, hours: i64, minutes: i64, seconds: i64) -> TimeSpan {
946        TimeSpan {
947            nanos: days * Self::DAY.nanos
948                + hours * Self::HOUR.nanos
949                + minutes * Self::MINUTE.nanos
950                + seconds * Self::SECOND.nanos,
951        }
952    }
953
954    /// Returns time span from years, days, hours, minutes and seconds.
955    ///
956    /// This function uses gregorian year length of 365.2425 days.
957    #[inline]
958    #[must_use]
959    pub const fn ydhms(years: i64, days: i64, hours: i64, minutes: i64, seconds: i64) -> TimeSpan {
960        TimeSpan {
961            nanos: years * Self::GREGORIAN_YEAR.nanos
962                + days * Self::DAY.nanos
963                + hours * Self::HOUR.nanos
964                + minutes * Self::MINUTE.nanos
965                + seconds * Self::SECOND.nanos,
966        }
967    }
968}
969
970impl Add<TimeSpan> for TimeSpan {
971    type Output = Self;
972
973    #[inline]
974    fn add(self, rhs: TimeSpan) -> Self {
975        self.checked_add(rhs).expect("overflow when adding spans")
976    }
977}
978
979impl AddAssign<TimeSpan> for TimeSpan {
980    fn add_assign(&mut self, rhs: TimeSpan) {
981        *self = *self + rhs;
982    }
983}
984
985impl Sub<TimeSpan> for TimeSpan {
986    type Output = TimeSpan;
987
988    #[inline]
989    fn sub(self, rhs: TimeSpan) -> Self {
990        self.checked_sub(rhs)
991            .expect("overflow when subtracting spans")
992    }
993}
994
995impl SubAssign<TimeSpan> for TimeSpan {
996    fn sub_assign(&mut self, rhs: TimeSpan) {
997        *self = *self - rhs;
998    }
999}
1000
1001impl Div<TimeSpan> for TimeSpan {
1002    type Output = i64;
1003
1004    #[inline]
1005    fn div(self, rhs: TimeSpan) -> i64 {
1006        self.checked_div_span(rhs)
1007            .expect("divide by zero error when dividing span by span")
1008    }
1009}
1010
1011impl Rem<TimeSpan> for TimeSpan {
1012    type Output = TimeSpan;
1013
1014    #[inline]
1015    fn rem(self, rhs: TimeSpan) -> TimeSpan {
1016        self.checked_rem_span(rhs)
1017            .expect("divide by zero error when dividing span by span")
1018    }
1019}
1020
1021impl RemAssign<TimeSpan> for TimeSpan {
1022    #[inline]
1023    fn rem_assign(&mut self, rhs: TimeSpan) {
1024        *self = *self % rhs;
1025    }
1026}
1027
1028impl Mul<i64> for TimeSpan {
1029    type Output = Self;
1030
1031    #[inline]
1032    fn mul(self, rhs: i64) -> Self {
1033        self.checked_mul(rhs)
1034            .expect("overflow when multiplying span by scalar")
1035    }
1036}
1037
1038impl Mul<TimeSpan> for i64 {
1039    type Output = TimeSpan;
1040
1041    #[inline]
1042    fn mul(self, rhs: TimeSpan) -> TimeSpan {
1043        rhs * self
1044    }
1045}
1046
1047impl MulAssign<i64> for TimeSpan {
1048    #[inline]
1049    fn mul_assign(&mut self, rhs: i64) {
1050        *self = *self * rhs;
1051    }
1052}
1053
1054impl Div<i64> for TimeSpan {
1055    type Output = TimeSpan;
1056
1057    #[inline]
1058    fn div(self, rhs: i64) -> Self {
1059        self.checked_div(rhs)
1060            .expect("divide by zero error when dividing span by scalar")
1061    }
1062}
1063
1064impl DivAssign<i64> for TimeSpan {
1065    #[inline]
1066    fn div_assign(&mut self, rhs: i64) {
1067        *self = *self / rhs;
1068    }
1069}
1070
1071impl Rem<i64> for TimeSpan {
1072    type Output = TimeSpan;
1073
1074    #[inline]
1075    fn rem(self, rhs: i64) -> Self {
1076        self.checked_rem(rhs)
1077            .expect("divide by zero error when dividing span by scalar")
1078    }
1079}
1080
1081impl RemAssign<i64> for TimeSpan {
1082    #[inline]
1083    fn rem_assign(&mut self, rhs: i64) {
1084        *self = *self % rhs;
1085    }
1086}
1087
1088/// This trait adds methods to integers to convert values into `TimeSpan`s.
1089pub trait TimeSpanNumExt {
1090    /// Convert integer value into `TimeSpan` with that amount of nanoseconds.
1091    fn nanoseconds(self) -> TimeSpan;
1092
1093    /// Convert integer value into `TimeSpan` with that amount of microseconds.
1094    fn microseconds(self) -> TimeSpan;
1095
1096    /// Convert integer value into `TimeSpan` with that amount of milliseconds.
1097    fn milliseconds(self) -> TimeSpan;
1098
1099    /// Convert integer value into `TimeSpan` with that amount of seconds.
1100    fn seconds(self) -> TimeSpan;
1101
1102    /// Convert integer value into `TimeSpan` with that amount of minutes.
1103    fn minutes(self) -> TimeSpan;
1104
1105    /// Convert integer value into `TimeSpan` with that amount of hours.
1106    fn hours(self) -> TimeSpan;
1107
1108    /// Convert integer value into `TimeSpan` with that amount of days.
1109    fn days(self) -> TimeSpan;
1110}
1111
1112macro_rules! impl_for_int {
1113    ($($int:ty)*) => {
1114        $(
1115            impl_for_int!(@ $int);
1116        )*
1117    };
1118
1119    (@ $int:ty) => {
1120        impl TimeSpanNumExt for $int {
1121            #[inline]
1122            fn nanoseconds(self) -> TimeSpan {
1123                TimeSpan::NANOSECOND * i64::from(self)
1124            }
1125            #[inline]
1126            fn microseconds(self) -> TimeSpan {
1127                TimeSpan::MICROSECOND * i64::from(self)
1128            }
1129            #[inline]
1130            fn milliseconds(self) -> TimeSpan {
1131                TimeSpan::MILLISECOND * i64::from(self)
1132            }
1133            #[inline]
1134            fn seconds(self) -> TimeSpan {
1135                TimeSpan::SECOND * i64::from(self)
1136            }
1137            #[inline]
1138            fn minutes(self) -> TimeSpan {
1139                TimeSpan::MINUTE * i64::from(self)
1140            }
1141            #[inline]
1142            fn hours(self) -> TimeSpan {
1143                TimeSpan::HOUR * i64::from(self)
1144            }
1145            #[inline]
1146            fn days(self) -> TimeSpan {
1147                TimeSpan::DAY * i64::from(self)
1148            }
1149        }
1150    };
1151}
1152
1153impl_for_int!(i64);
1154
1155#[test]
1156fn test_span_print() {
1157    assert_eq!("1d00:00", TimeSpan::DAY.to_string());
1158    assert_eq!("1:00:00", TimeSpan::HOUR.to_string());
1159    assert_eq!("1:00", TimeSpan::MINUTE.to_string());
1160    assert_eq!("1s", TimeSpan::SECOND.to_string());
1161
1162    assert_eq!(
1163        "1:02:11",
1164        (TimeSpan::HOUR + 2 * TimeSpan::MINUTE + 11 * TimeSpan::SECOND).to_string()
1165    );
1166
1167    assert_eq!(
1168        "2:11.011",
1169        (2 * TimeSpan::MINUTE + 11 * TimeSpan::SECOND + 11 * TimeSpan::MILLISECOND).to_string()
1170    );
1171
1172    assert_eq!(
1173        "2:11.011",
1174        (2 * TimeSpan::MINUTE + 11 * TimeSpan::SECOND + 11 * TimeSpan::MILLISECOND).to_string()
1175    );
1176}
1177
1178#[test]
1179fn test_span_parse() {
1180    assert_eq!("1d00:00".parse::<TimeSpan>().unwrap(), TimeSpan::DAY);
1181    assert_eq!("1:00:00".parse::<TimeSpan>().unwrap(), TimeSpan::HOUR);
1182    assert_eq!("1:00".parse::<TimeSpan>().unwrap(), TimeSpan::MINUTE);
1183    assert_eq!("1s".parse::<TimeSpan>().unwrap(), TimeSpan::SECOND);
1184
1185    assert_eq!(
1186        "1:02:11".parse::<TimeSpan>().unwrap(),
1187        TimeSpan::HOUR + 2 * TimeSpan::MINUTE + 11 * TimeSpan::SECOND
1188    );
1189
1190    assert_eq!(
1191        "2:11.011".parse::<TimeSpan>().unwrap(),
1192        2 * TimeSpan::MINUTE + 11 * TimeSpan::SECOND + 11 * TimeSpan::MILLISECOND
1193    );
1194
1195    assert_eq!(
1196        "2:11.011".parse::<TimeSpan>().unwrap(),
1197        2 * TimeSpan::MINUTE + 11 * TimeSpan::SECOND + 11 * TimeSpan::MILLISECOND
1198    );
1199}