1use 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#[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 const MAX_DISPLAY_LENGTH: usize = 26;
141
142 pub const DISPLAY_BUFFER: [u8; Self::MAX_DISPLAY_LENGTH] = [0; Self::MAX_DISPLAY_LENGTH];
144
145 pub fn display_to_buffer(self, buf: &mut [u8; Self::MAX_DISPLAY_LENGTH]) -> &mut str {
150 #![allow(clippy::missing_panics_doc)] 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 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 #[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 #[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 pub const ZERO: Self = TimeSpan { nanos: 0 };
665
666 pub const MIN: Self = TimeSpan {
668 nanos: -i64::MAX,
672 };
673
674 pub const MAX: Self = TimeSpan {
676 nanos: i64::MAX,
678 };
679
680 pub const NANOSECOND: Self = TimeSpan { nanos: 1 };
683
684 pub const MICROSECOND: Self = TimeSpan { nanos: 1_000 };
686
687 pub const MILLISECOND: Self = TimeSpan { nanos: 1_000_000 };
689
690 pub const SECOND: Self = TimeSpan {
692 nanos: 1_000_000_000,
693 };
694
695 pub const MINUTE: Self = TimeSpan {
697 nanos: 60_000_000_000,
698 };
699
700 pub const HOUR: Self = TimeSpan {
702 nanos: 3_600_000_000_000,
703 };
704
705 pub const DAY: Self = TimeSpan {
707 nanos: 86_400_000_000_000,
708 };
709
710 pub const WEEK: Self = TimeSpan {
713 nanos: 604_800_000_000_000,
714 };
715
716 pub const JULIAN_YEAR: Self = TimeSpan {
720 nanos: 31_557_600_000_000_000,
721 };
722
723 pub const GREGORIAN_YEAR: Self = TimeSpan {
727 nanos: 31_556_952_000_000,
728 };
729
730 pub const SOLAR_YEAR: Self = TimeSpan {
733 nanos: 31_556_925_216_000_000,
734 };
735
736 pub const YEAR: Self = Self::GREGORIAN_YEAR;
739
740 #[inline]
742 #[must_use]
743 pub const fn new(nanos: i64) -> TimeSpan {
744 TimeSpan { nanos }
745 }
746
747 #[inline]
749 #[must_use]
750 pub const fn as_nanos(self) -> i64 {
751 self.nanos
752 }
753
754 #[inline]
756 #[must_use]
757 pub const fn as_micros(self) -> i64 {
758 self.nanos / Self::MICROSECOND.nanos
759 }
760
761 #[inline]
763 #[must_use]
764 pub const fn as_millis(self) -> i64 {
765 self.nanos / Self::MILLISECOND.nanos
766 }
767
768 #[inline]
770 #[must_use]
771 pub const fn as_seconds(self) -> i64 {
772 self.nanos / Self::SECOND.nanos
773 }
774
775 #[inline]
777 #[must_use]
778 pub const fn as_minutes(self) -> i64 {
779 self.nanos / Self::MINUTE.nanos
780 }
781
782 #[inline]
784 #[must_use]
785 pub const fn as_hours(self) -> i64 {
786 self.nanos / Self::HOUR.nanos
787 }
788
789 #[inline]
791 #[must_use]
792 pub const fn as_days(self) -> i64 {
793 self.nanos / Self::DAY.nanos
794 }
795
796 #[inline]
798 #[must_use]
799 pub const fn as_weeks(self) -> i64 {
800 self.nanos / Self::WEEK.nanos
801 }
802
803 #[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 #[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 #[inline]
824 #[must_use]
825 pub fn abs(self) -> TimeSpan {
826 TimeSpan {
827 nanos: self.nanos.abs(),
828 }
829 }
830
831 #[inline]
833 #[must_use]
834 pub fn is_negative(self) -> bool {
835 self.nanos < 0
836 }
837
838 #[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 #[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 #[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 #[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 #[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 #[inline]
890 #[must_use]
891 pub const fn div_span(self, span: TimeSpan) -> i64 {
892 self.nanos / span.nanos
893 }
894
895 #[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 #[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 #[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 #[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 #[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 #[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 #[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
1088pub trait TimeSpanNumExt {
1090 fn nanoseconds(self) -> TimeSpan;
1092
1093 fn microseconds(self) -> TimeSpan;
1095
1096 fn milliseconds(self) -> TimeSpan;
1098
1099 fn seconds(self) -> TimeSpan;
1101
1102 fn minutes(self) -> TimeSpan;
1104
1105 fn hours(self) -> TimeSpan;
1107
1108 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}