tinytime/
lib.rs

1//! Low overhead implementation of time related concepts.
2//!
3//!  # Operator support
4//!
5//! ```no_run
6//! # use tinytime::Duration;
7//! # use tinytime::Time;
8//! # use tinytime::TimeWindow;
9//! # let mut time = Time::hours(3);
10//! # let mut duration = Duration::minutes(4);
11//! # let mut time_window = TimeWindow::new(Time::hours(2), Time::hours(3));
12//! // | example                                       | left       | op | right    | result     |
13//! // | ----------------------------------------------| ---------- | ---| -------- | ---------- |
14//! let result: Duration = time - time;             // | Time       | -  | Time     | Duration   |
15//! let result: Time = time + duration;             // | Time       | +  | Duration | Time       |
16//! time += duration;                               // | Time       | += | Duration | Time       |
17//! let result: Time = time - duration;             // | Time       | -  | Duration | Time       |
18//! time -= duration;                               // | Time       | -= | Duration | Time       |
19//! let result: Duration = duration + duration;     // | Duration   | +  | Duration | Duration   |
20//! duration += duration;                           // | Duration   | += | Duration | Duration   |
21//! let result: Duration = duration - duration;     // | Duration   | -  | Duration | Duration   |
22//! duration -= duration;                           // | Duration   | -= | Duration | Duration   |
23//! let result: Duration = duration * 1.0f64;       // | Duration   | *  | f64      | Duration   |
24//! let result: Duration = 2.0f64 * duration;       // | f64        | *  | Duration | Duration   |
25//! duration *= 2.0f64;                             // | Duration   | *= | f64      | Duration   |
26//! let result: Duration = duration / 2.0f64;       // | Duration   | /  | f64      | Duration   |
27//! duration /= 2.0f64;                             // | Duration   | /= | f64      | Duration   |
28//! let result: Duration = duration * 7i64;         // | Duration   | *  | i64      | Duration   |
29//! let result: Duration = 7i64 * duration;         // | i64        | *  | Duration | Duration   |
30//! duration *= 7i64;                               // | Duration   | *= | i64      | Duration   |
31//! let result: Duration = duration / 7i64;         // | Duration   | /  | i64      | Duration   |
32//! duration /= 7i64;                               // | Duration   | /= | i64      | Duration   |
33//! let result: f64 = duration / duration;          // | Duration   | /  | Duration | f64        |
34
35//! ```
36
37#[cfg(feature = "chrono")]
38pub mod chrono;
39#[cfg(feature = "rand")]
40pub mod rand;
41#[cfg(feature = "serde")]
42pub mod serde;
43
44use core::fmt;
45use std::cmp::Ordering;
46use std::cmp::max;
47use std::cmp::min;
48use std::error::Error;
49use std::fmt::Debug;
50use std::fmt::Display;
51use std::fmt::Formatter;
52use std::iter::Sum;
53use std::ops::Add;
54use std::ops::AddAssign;
55use std::ops::Deref;
56use std::ops::Div;
57use std::ops::DivAssign;
58use std::ops::Mul;
59use std::ops::MulAssign;
60use std::ops::Neg;
61use std::ops::Rem;
62use std::ops::RemAssign;
63use std::ops::Sub;
64use std::ops::SubAssign;
65use std::str::FromStr;
66use std::time::SystemTime;
67
68/// A point in time.
69///
70/// Low overhead time representation. Internally represented as milliseconds.
71#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Copy, Clone, Default)]
72#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
73#[cfg_attr(feature = "bincode", derive(::bincode::Encode, ::bincode::Decode))]
74pub struct Time(i64);
75
76impl Time {
77    pub const MAX: Self = Self(i64::MAX);
78    pub const EPOCH: Self = Self(0);
79
80    const SECOND: Time = Time(1000);
81    const MINUTE: Time = Time(60 * Self::SECOND.0);
82    const HOUR: Time = Time(60 * Self::MINUTE.0);
83
84    #[must_use]
85    pub const fn millis(millis: i64) -> Self {
86        Time(millis)
87    }
88
89    #[must_use]
90    pub const fn seconds(seconds: i64) -> Self {
91        Time::millis(seconds * Self::SECOND.0)
92    }
93
94    #[must_use]
95    pub const fn minutes(minutes: i64) -> Self {
96        Time::millis(minutes * Self::MINUTE.0)
97    }
98
99    #[must_use]
100    pub const fn hours(hours: i64) -> Self {
101        Time::millis(hours * Self::HOUR.0)
102    }
103
104    /// Returns the current time instance based on `SystemTime`
105    ///
106    /// Don't use this method to compare if the current time has passed a
107    /// certain deadline.
108    #[must_use]
109    pub fn now() -> Time {
110        Time::from(SystemTime::now())
111    }
112
113    /// Returns the number of whole seconds in the time.
114    ///
115    /// # Examples
116    ///
117    /// ```
118    /// # use tinytime::Time;
119    /// assert_eq!(Time::minutes(1).as_seconds(), 60);
120    /// ```
121    #[must_use]
122    pub const fn as_seconds(&self) -> i64 {
123        self.0 / Self::SECOND.0
124    }
125
126    #[must_use]
127    pub const fn as_millis(&self) -> i64 {
128        self.0
129    }
130
131    /// Returns the number of subsecond millis converted to nanos.
132    ///
133    /// # Examples
134    ///
135    /// ```
136    /// # use tinytime::Time;
137    /// assert_eq!(Time::millis(12345).as_subsecond_nanos(), 345_000_000);
138    /// ```
139    #[must_use]
140    pub const fn as_subsecond_nanos(&self) -> i32 {
141        (self.0 % Self::SECOND.0 * 1_000_000) as i32
142    }
143
144    /// Rounds time down to a step size
145    ///
146    /// # Examples
147    ///
148    /// ```
149    /// # use tinytime::Duration;
150    /// # use tinytime::Time;
151    /// assert_eq!(
152    ///     Time::minutes(7).round_down(Duration::minutes(5)),
153    ///     Time::minutes(5)
154    /// );
155    /// assert_eq!(
156    ///     Time::minutes(5).round_down(Duration::minutes(5)),
157    ///     Time::minutes(5)
158    /// );
159    /// ```
160    #[must_use]
161    pub const fn round_down(&self, step_size: Duration) -> Time {
162        let time_milli = self.as_millis();
163        let part = time_milli % step_size.as_millis().abs();
164        Time::millis(time_milli - part)
165    }
166
167    /// Rounds time up to a step size
168    ///
169    /// # Examples
170    ///
171    /// ```
172    /// # use tinytime::Duration;
173    /// # use tinytime::Time;
174    /// assert_eq!(
175    ///     Time::minutes(7).round_up(Duration::minutes(5)),
176    ///     Time::minutes(10)
177    /// );
178    /// assert_eq!(
179    ///     Time::minutes(5).round_up(Duration::minutes(5)),
180    ///     Time::minutes(5)
181    /// );
182    /// ```
183    #[must_use]
184    pub const fn round_up(&self, step_size: Duration) -> Time {
185        let time_milli = self.as_millis();
186        let step_milli = step_size.as_millis().abs();
187        let part = time_milli % step_milli;
188        let remaining = (step_milli - part) % step_milli;
189        Time::millis(time_milli + remaining)
190    }
191
192    /// Checked time duration substraction. Computes `self - rhs`, returning
193    /// `None` if overflow occurred.
194    ///
195    /// # Examples
196    /// ```
197    /// # use tinytime::Duration;
198    /// # use tinytime::Time;
199    /// assert_eq!(
200    ///     Time::minutes(8).checked_sub(Duration::minutes(5)),
201    ///     Some(Time::minutes(3))
202    /// );
203    /// assert_eq!(Time::minutes(3).checked_sub(Duration::minutes(5)), None);
204    /// assert_eq!(
205    ///     Time::minutes(2).checked_sub(Duration::minutes(2)),
206    ///     Some(Time::EPOCH)
207    /// );
208    /// ```
209    #[must_use]
210    pub fn checked_sub(&self, rhs: Duration) -> Option<Self> {
211        // check for overflow
212        if Time::EPOCH + rhs > *self {
213            None
214        } else {
215            Some(*self - rhs)
216        }
217    }
218
219    #[must_use]
220    pub const fn since_epoch(&self) -> Duration {
221        Duration::millis(self.as_millis())
222    }
223}
224
225impl Deref for Time {
226    type Target = i64;
227
228    fn deref(&self) -> &Self::Target {
229        &self.0
230    }
231}
232
233impl From<Time> for i64 {
234    fn from(time: Time) -> Self {
235        time.0
236    }
237}
238
239impl From<i64> for Time {
240    fn from(value: i64) -> Self {
241        Self(value)
242    }
243}
244
245impl TryFrom<Duration> for Time {
246    type Error = &'static str;
247    fn try_from(duration: Duration) -> Result<Self, Self::Error> {
248        if duration.is_non_negative() {
249            Ok(Time::millis(duration.as_millis()))
250        } else {
251            Err("Duration cannot be negative.")
252        }
253    }
254}
255
256#[derive(Debug, Copy, Clone)]
257pub struct TimeIsNegativeError;
258
259impl TryFrom<Time> for SystemTime {
260    type Error = TimeIsNegativeError;
261
262    fn try_from(input: Time) -> Result<Self, Self::Error> {
263        u64::try_from(input.0).map_or(Err(TimeIsNegativeError), |t| {
264            Ok(std::time::UNIX_EPOCH + std::time::Duration::from_millis(t))
265        })
266    }
267}
268
269impl From<SystemTime> for Time {
270    fn from(input: SystemTime) -> Self {
271        let duration = match input.duration_since(SystemTime::UNIX_EPOCH) {
272            Ok(std_dur) => Duration::from(std_dur),
273            Err(err) => -Duration::from(err.duration()),
274        };
275        Self::millis(duration.as_millis())
276    }
277}
278
279impl Debug for Time {
280    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
281        // This implementation is tailor-made, because NaiveDateTime does not support
282        // the full range of Time. For some Time instances it wouldn't be
283        // possible to reconstruct them based on the Debug-representation ('∞').
284        let positive = self.0 >= 0;
285        let mut total = self.0.unsigned_abs();
286        let millis_part = total % 1000;
287        total -= millis_part;
288        let seconds_part = (total % (1000 * 60)) / 1000;
289        total -= seconds_part;
290        let minutes_part = (total % (1000 * 60 * 60)) / (1000 * 60);
291        total -= minutes_part;
292        let hours_part = total / (1000 * 60 * 60);
293        if !positive {
294            f.write_str("-")?;
295        }
296        write!(f, "{hours_part:02}:")?;
297        write!(f, "{minutes_part:02}:")?;
298        write!(f, "{seconds_part:02}")?;
299        if millis_part > 0 {
300            write!(f, ".{millis_part:03}")?;
301        }
302        Ok(())
303    }
304}
305
306#[derive(Debug, Eq, PartialEq, Clone, Copy)]
307pub enum TimeWindowError {
308    StartAfterEnd,
309}
310
311impl Display for TimeWindowError {
312    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
313        let message = match self {
314            Self::StartAfterEnd => "time window start is after end",
315        };
316        write!(f, "{message}")
317    }
318}
319
320impl Error for TimeWindowError {}
321
322/// An interval or range of time: `[start,end)`.
323/// Debug-asserts ensure that start <= end.
324/// If compiled in release mode, the invariant of start <= end is maintained, by
325/// correcting invalid use of the API (and setting end to start).
326#[derive(Clone, Debug, Eq, PartialEq, Default, Copy, Hash)]
327#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
328#[cfg_attr(feature = "bincode", derive(::bincode::Encode, ::bincode::Decode))]
329pub struct TimeWindow {
330    start: Time,
331    end: Time,
332}
333
334impl TimeWindow {
335    /// Constructs a new [`TimeWindow`].
336    /// `debug_asserts` that `start < end`. Sets end to `start` in release mode
337    /// if `start > end`.
338    #[must_use]
339    pub fn new(start: Time, end: Time) -> Self {
340        debug_assert!(start <= end);
341        TimeWindow {
342            start,
343            end: end.max(start),
344        }
345    }
346
347    /// Constructs a new [`TimeWindow`]. Validates that `start <= end` and
348    /// returns an error if not.
349    ///
350    /// # Examples
351    /// ```
352    /// # use tinytime::*;
353    /// assert!(TimeWindow::new_checked(Time::hours(1), Time::hours(2)).is_ok());
354    /// assert_eq!(
355    ///     Err(TimeWindowError::StartAfterEnd),
356    ///     TimeWindow::new_checked(Time::hours(2), Time::hours(1))
357    /// );
358    /// ```
359    pub fn new_checked(start: Time, end: Time) -> Result<Self, TimeWindowError> {
360        if start <= end {
361            Ok(TimeWindow { start, end })
362        } else {
363            Err(TimeWindowError::StartAfterEnd)
364        }
365    }
366
367    /// Returns [`TimeWindow`] with range [[`Time::EPOCH`], `end`)
368    #[must_use]
369    pub fn epoch_to(end: Time) -> Self {
370        Self::new(Time::EPOCH, end)
371    }
372
373    #[must_use]
374    pub fn from_minutes(a: i64, b: i64) -> Self {
375        TimeWindow::new(Time::minutes(a), Time::minutes(b))
376    }
377
378    #[must_use]
379    pub fn from_seconds(a: i64, b: i64) -> Self {
380        TimeWindow::new(Time::seconds(a), Time::seconds(b))
381    }
382
383    /// Creates time window from start time and length.
384    ///
385    /// Negative lengths are treated as [`Duration::ZERO`].
386    ///
387    /// # Examples
388    /// ```
389    /// # use tinytime::*;
390    /// assert_eq!(
391    ///     TimeWindow::from_seconds(1, 3),
392    ///     TimeWindow::from_length_starting_at(Duration::seconds(2), Time::seconds(1))
393    /// );
394    /// assert_eq!(
395    ///     TimeWindow::from_seconds(1, 1),
396    ///     TimeWindow::from_length_starting_at(Duration::seconds(-2), Time::seconds(1))
397    /// );
398    /// ```
399    #[must_use]
400    pub fn from_length_starting_at(length: Duration, start: Time) -> Self {
401        TimeWindow::new(start, start.add(length.max(Duration::ZERO)))
402    }
403
404    /// Creates time window from length and end time.
405    ///
406    /// Negative lengths are treated as [`Duration::ZERO`].
407    ///
408    ///  # Examples
409    /// ```
410    /// # use tinytime::*;
411    /// assert_eq!(
412    ///     TimeWindow::from_seconds(1, 3),
413    ///     TimeWindow::from_length_ending_at(Duration::seconds(2), Time::seconds(3))
414    /// );
415    /// assert_eq!(
416    ///     TimeWindow::from_seconds(3, 3),
417    ///     TimeWindow::from_length_ending_at(Duration::seconds(-2), Time::seconds(3))
418    /// );
419    /// ```
420    #[must_use]
421    pub fn from_length_ending_at(length: Duration, end: Time) -> Self {
422        TimeWindow::new(end.sub(length.max(Duration::ZERO)), end)
423    }
424
425    #[must_use]
426    pub const fn instant(time: Time) -> Self {
427        TimeWindow {
428            start: time,
429            end: time,
430        }
431    }
432
433    #[must_use]
434    pub const fn widest() -> Self {
435        TimeWindow {
436            start: Time::EPOCH,
437            end: Time::MAX,
438        }
439    }
440
441    #[must_use]
442    pub fn instant_seconds(seconds: i64) -> Self {
443        TimeWindow::from_seconds(seconds, seconds)
444    }
445
446    #[must_use]
447    pub const fn start(&self) -> Time {
448        self.start
449    }
450
451    #[must_use]
452    pub const fn end(&self) -> Time {
453        self.end
454    }
455
456    #[must_use]
457    pub fn length(&self) -> Duration {
458        self.end - self.start
459    }
460
461    /// Creates a new `TimeWindow` with `start` set to `new_start`. If
462    /// `new_start` is greater than or equal to `end` the start will be set
463    /// equal to `end`.
464    #[must_use]
465    pub fn with_start(&self, new_start: Time) -> Self {
466        Self::new(new_start.min(self.end), self.end)
467    }
468
469    /// Creates a new `TimeWindow` with `end` set to `new_end`. If `new_end` is
470    /// smaller or equal to `start`, the `end` will be set to `start.`
471    #[must_use]
472    pub fn with_end(&self, new_end: Time) -> Self {
473        Self::new(self.start, new_end.max(self.start))
474    }
475
476    /// Creates a new `TimeWindow` with the `start` preponed to the given value.
477    /// If `new_start` isn't earlier than the current time window start, a copy
478    /// of `self` is returned.
479    ///
480    /// # Examples
481    /// ```
482    /// # use tinytime::*;
483    /// let x = TimeWindow::from_seconds(4, 5);
484    /// assert_eq!(
485    ///     TimeWindow::from_seconds(3, 5),
486    ///     x.prepone_start_to(Time::seconds(3))
487    /// );
488    /// assert_eq!(
489    ///     TimeWindow::from_seconds(4, 5),
490    ///     x.prepone_start_to(Time::seconds(6))
491    /// );
492    /// ```
493    #[must_use]
494    pub fn prepone_start_to(&self, new_start: Time) -> Self {
495        self.with_start(self.start.min(new_start))
496    }
497
498    /// Creates a new `TimeWindow` with the `start` preponed by the given
499    /// duration.
500    ///
501    /// Negative durations are treated as [`Duration::ZERO`].
502    ///
503    /// # Examples
504    /// ```
505    /// # use tinytime::*;
506    /// let tw = TimeWindow::from_seconds(8, 9);
507    /// assert_eq!(
508    ///     TimeWindow::from_seconds(5, 9),
509    ///     tw.prepone_start_by(Duration::seconds(3))
510    /// );
511    /// assert_eq!(
512    ///     TimeWindow::from_seconds(8, 9),
513    ///     tw.prepone_start_by(Duration::seconds(-3))
514    /// );
515    /// ```
516    #[must_use]
517    pub fn prepone_start_by(&self, duration: Duration) -> Self {
518        self.with_start(self.start - duration.max(Duration::ZERO))
519    }
520
521    /// Creates a new `TimeWindow` with the `start` preponed so that the new
522    /// time window length matches the given value.
523    ///
524    /// Returns a copy of `self` if the new length is smaller than
525    /// [`Self::length()`].
526    ///
527    /// # Examples
528    /// ```
529    /// # use tinytime::*;
530    /// let tw = TimeWindow::from_seconds(1, 3);
531    /// assert_eq!(
532    ///     TimeWindow::from_seconds(1, 3),
533    ///     tw.prepone_start_extend_to(Duration::seconds(-1))
534    /// );
535    /// assert_eq!(
536    ///     TimeWindow::from_seconds(1, 3),
537    ///     tw.prepone_start_extend_to(Duration::seconds(0))
538    /// );
539    /// assert_eq!(
540    ///     TimeWindow::from_seconds(-2, 3),
541    ///     tw.prepone_start_extend_to(Duration::seconds(5))
542    /// );
543    /// ```
544    #[must_use]
545    pub fn prepone_start_extend_to(&self, new_length: Duration) -> Self {
546        self.with_start(self.end - new_length.max(self.length()))
547    }
548
549    /// Creates a new `TimeWindow` with the `start` postponed to the given
550    /// value.
551    ///
552    /// Returns a copy of `self` when the given value isn't later than the
553    /// current time window start. Will never postpone the start past the
554    /// end of the time window.
555    ///
556    /// # Examples
557    /// ```
558    /// # use tinytime::*;
559    /// let tw = TimeWindow::from_seconds(1, 3);
560    /// assert_eq!(
561    ///     TimeWindow::from_seconds(1, 3),
562    ///     tw.postpone_start_to(Time::EPOCH)
563    /// );
564    /// assert_eq!(
565    ///     TimeWindow::from_seconds(2, 3),
566    ///     tw.postpone_start_to(Time::seconds(2))
567    /// );
568    /// assert_eq!(
569    ///     TimeWindow::from_seconds(3, 3),
570    ///     tw.postpone_start_to(Time::seconds(3))
571    /// );
572    /// ```
573    #[must_use]
574    pub fn postpone_start_to(&self, new_start: Time) -> Self {
575        self.with_start(self.start.max(new_start))
576    }
577
578    /// Creates a new `TimeWindow` with the `start` postponed by the given
579    /// duration.
580    ///
581    /// Negative durations are treated as [`Duration::ZERO`]. Will not postpone
582    /// `start` further than `end`.
583    ///
584    /// # Examples
585    /// ```
586    /// # use tinytime::*;
587    /// let tw = TimeWindow::from_seconds(1, 5);
588    /// assert_eq!(
589    ///     TimeWindow::from_seconds(4, 5),
590    ///     tw.postpone_start_by(Duration::seconds(3))
591    /// );
592    /// assert_eq!(
593    ///     TimeWindow::from_seconds(5, 5),
594    ///     tw.postpone_start_by(Duration::seconds(30))
595    /// );
596    /// assert_eq!(
597    ///     TimeWindow::from_seconds(1, 5),
598    ///     tw.postpone_start_by(Duration::seconds(-3))
599    /// );
600    /// ```
601    #[must_use]
602    pub fn postpone_start_by(&self, duration: Duration) -> Self {
603        self.with_start(self.start + duration.max(Duration::ZERO))
604    }
605
606    /// Creates a new `TimeWindow` with the `start` postponed so that the new
607    /// time window length matches the given value.
608    ///
609    /// Returns a copy of `self` if the new length is smaller than the current
610    /// one. Negative length will set the resulting time window length to zero.
611    ///
612    /// # Examples
613    /// ```
614    /// # use tinytime::*;
615    /// let tw = TimeWindow::from_seconds(1, 3);
616    /// assert_eq!(
617    ///     TimeWindow::from_seconds(3, 3),
618    ///     tw.postpone_start_shrink_to(Duration::seconds(-1))
619    /// );
620    /// assert_eq!(
621    ///     TimeWindow::from_seconds(3, 3),
622    ///     tw.postpone_start_shrink_to(Duration::seconds(0))
623    /// );
624    /// assert_eq!(
625    ///     TimeWindow::from_seconds(2, 3),
626    ///     tw.postpone_start_shrink_to(Duration::seconds(1))
627    /// );
628    /// assert_eq!(
629    ///     TimeWindow::from_seconds(1, 3),
630    ///     tw.postpone_start_shrink_to(Duration::seconds(5))
631    /// );
632    /// ```
633    #[must_use]
634    pub fn postpone_start_shrink_to(&self, new_length: Duration) -> Self {
635        let length = new_length
636            .min(self.length()) // Resize only if new length is smaller than the current one
637            .max(Duration::ZERO); // Make sure the new length is non-negative
638        self.with_start(self.end - length)
639    }
640
641    /// Creates a new `TimeWindow` with the `end` preponed to the given value.
642    ///
643    /// Returns a copy of `self` when the given value isn't earlier than the
644    /// current time window end. Will never prepone the end more than to the
645    /// start of the time window.
646    ///
647    /// # Examples
648    /// ```
649    /// # use tinytime::*;
650    /// let tw = TimeWindow::from_seconds(1, 3);
651    /// assert_eq!(
652    ///     TimeWindow::from_seconds(1, 3),
653    ///     tw.prepone_end_to(Time::seconds(4))
654    /// );
655    /// assert_eq!(
656    ///     TimeWindow::from_seconds(1, 2),
657    ///     tw.prepone_end_to(Time::seconds(2))
658    /// );
659    /// assert_eq!(
660    ///     TimeWindow::from_seconds(1, 1),
661    ///     tw.prepone_end_to(Time::EPOCH)
662    /// );
663    /// ```
664    #[must_use]
665    pub fn prepone_end_to(&self, new_end: Time) -> Self {
666        self.with_end(self.end.min(new_end))
667    }
668
669    /// Creates a new `TimeWindow` with the `end` preponed by the given
670    /// duration.
671    ///
672    /// Negative durations are treated as [`Duration::ZERO`]. Will not prepone
673    /// `end` before `end`.
674    ///
675    /// # Examples
676    /// ```
677    /// # use tinytime::*;
678    /// let tw = TimeWindow::from_seconds(4, 9);
679    /// assert_eq!(
680    ///     TimeWindow::from_seconds(4, 6),
681    ///     tw.prepone_end_by(Duration::seconds(3))
682    /// );
683    /// assert_eq!(
684    ///     TimeWindow::from_seconds(4, 4),
685    ///     tw.prepone_end_by(Duration::seconds(30))
686    /// );
687    /// assert_eq!(
688    ///     TimeWindow::from_seconds(4, 9),
689    ///     tw.prepone_end_by(Duration::seconds(-3))
690    /// );
691    /// ```
692    #[must_use]
693    pub fn prepone_end_by(&self, duration: Duration) -> Self {
694        self.with_end(self.end - duration.max(Duration::ZERO))
695    }
696
697    /// Creates a new `TimeWindow` with the `end` preponed so that the new time
698    /// window length matches the given value.
699    ///
700    /// Returns a copy of `self` if the new length is smaller than the current
701    /// one. Negative length will set the resulting time window length to zero.
702    ///
703    /// # Examples
704    /// ```
705    /// # use tinytime::*;
706    /// let tw = TimeWindow::from_seconds(1, 3);
707    /// assert_eq!(
708    ///     TimeWindow::from_seconds(1, 1),
709    ///     tw.prepone_end_shrink_to(Duration::seconds(-1))
710    /// );
711    /// assert_eq!(
712    ///     TimeWindow::from_seconds(1, 1),
713    ///     tw.prepone_end_shrink_to(Duration::seconds(0))
714    /// );
715    /// assert_eq!(
716    ///     TimeWindow::from_seconds(1, 2),
717    ///     tw.prepone_end_shrink_to(Duration::seconds(1))
718    /// );
719    /// assert_eq!(
720    ///     TimeWindow::from_seconds(1, 3),
721    ///     tw.prepone_end_shrink_to(Duration::seconds(5))
722    /// );
723    /// ```
724    #[must_use]
725    pub fn prepone_end_shrink_to(&self, new_length: Duration) -> Self {
726        let length = new_length
727            .min(self.length()) // Resize only if new length is smaller than the current one
728            .max(Duration::ZERO); // Make sure the new length is non-negative
729        self.with_end(self.start + length)
730    }
731
732    /// Creates a new `TimeWindow` with the `end` postponed to the given value.
733    /// If `new_end` isn't later than the current time window end, a copy of
734    /// `self` is returned.
735    ///
736    /// # Examples
737    /// ```
738    /// # use tinytime::*;
739    /// let x = TimeWindow::from_seconds(1, 2);
740    /// assert_eq!(
741    ///     TimeWindow::from_seconds(1, 3),
742    ///     x.postpone_end_to(Time::seconds(3))
743    /// );
744    /// assert_eq!(
745    ///     TimeWindow::from_seconds(1, 2),
746    ///     x.postpone_end_to(Time::EPOCH)
747    /// );
748    /// ```
749    #[must_use]
750    pub fn postpone_end_to(&self, new_end: Time) -> Self {
751        self.with_end(self.end.max(new_end))
752    }
753
754    /// Creates a new `TimeWindow` with the `end` postponed by the given
755    /// duration.
756    ///
757    /// Negative durations are treated as [`Duration::ZERO`].
758    ///
759    /// # Examples
760    /// ```
761    /// # use tinytime::*;
762    /// let tw = TimeWindow::from_seconds(1, 2);
763    /// assert_eq!(
764    ///     TimeWindow::from_seconds(1, 5),
765    ///     tw.postpone_end_by(Duration::seconds(3))
766    /// );
767    /// assert_eq!(
768    ///     TimeWindow::from_seconds(1, 2),
769    ///     tw.postpone_end_by(Duration::seconds(-3))
770    /// );
771    /// ```
772    #[must_use]
773    pub fn postpone_end_by(&self, duration: Duration) -> Self {
774        self.with_end(self.end + duration.max(Duration::ZERO))
775    }
776
777    /// Creates a new `TimeWindow` with the `end` postponed so that the new
778    /// time window length matches the given value.
779    ///
780    /// Returns a copy of `self` if the new length is smaller than
781    /// [`Self::length()`].
782    ///
783    /// # Examples
784    /// ```
785    /// # use tinytime::*;
786    /// let tw = TimeWindow::from_seconds(1, 3);
787    /// assert_eq!(
788    ///     TimeWindow::from_seconds(1, 3),
789    ///     tw.postpone_end_extend_to(Duration::seconds(-1))
790    /// );
791    /// assert_eq!(
792    ///     TimeWindow::from_seconds(1, 3),
793    ///     tw.postpone_end_extend_to(Duration::seconds(0))
794    /// );
795    /// assert_eq!(
796    ///     TimeWindow::from_seconds(1, 6),
797    ///     tw.postpone_end_extend_to(Duration::seconds(5))
798    /// );
799    /// ```
800    #[must_use]
801    pub fn postpone_end_extend_to(&self, new_length: Duration) -> Self {
802        self.with_end(self.start + new_length.max(self.length()))
803    }
804
805    /// Returns true if this time window contains the given time.
806    /// # Examples
807    ///
808    /// ```
809    /// # use tinytime::{Time, TimeWindow};
810    /// let mut x = TimeWindow::from_seconds(5, 10);
811    /// assert!(!x.contains(Time::seconds(4)));
812    /// assert!(x.contains(Time::seconds(5)));
813    /// assert!(x.contains(Time::seconds(7)));
814    /// assert!(x.contains(Time::seconds(10)));
815    /// assert!(!x.contains(Time::seconds(11)));
816    /// ```
817    #[must_use]
818    pub fn contains(&self, that: Time) -> bool {
819        self.start <= that && that <= self.end
820    }
821
822    /// Returns true if this time window overlaps with another one
823    /// # Examples
824    ///
825    /// ```
826    /// # use tinytime::TimeWindow;
827    /// let mut x = TimeWindow::from_seconds(5, 10);
828    /// assert!(x.overlaps(&TimeWindow::from_seconds(5, 10)));
829    /// assert!(x.overlaps(&TimeWindow::from_seconds(3, 12)));
830    /// assert!(x.overlaps(&TimeWindow::from_seconds(6, 9)));
831    /// assert!(x.overlaps(&TimeWindow::from_seconds(6, 12)));
832    /// assert!(x.overlaps(&TimeWindow::from_seconds(3, 9)));
833    /// assert!(!x.overlaps(&TimeWindow::from_seconds(1, 4)));
834    /// assert!(!x.overlaps(&TimeWindow::from_seconds(1, 5)));
835    /// assert!(!x.overlaps(&TimeWindow::from_seconds(10, 15)));
836    /// assert!(!x.overlaps(&TimeWindow::from_seconds(11, 15)));
837    /// ```
838    #[must_use]
839    pub fn overlaps(&self, that: &TimeWindow) -> bool {
840        self.start < that.end && that.start < self.end
841    }
842
843    /// Returns time window that is an intersection between this time window and
844    /// another one. Returns None if time windows don't overlap.
845    /// # Examples
846    ///
847    /// ```
848    /// # use tinytime::TimeWindow;
849    /// let x = TimeWindow::from_seconds(5, 10);
850    /// assert_eq!(
851    ///     Some(TimeWindow::from_seconds(5, 10)),
852    ///     x.intersect(&TimeWindow::from_seconds(5, 10)),
853    ///     "time windows are equal"
854    /// );
855    /// assert_eq!(
856    ///     Some(TimeWindow::from_seconds(5, 10)),
857    ///     x.intersect(&TimeWindow::from_seconds(3, 12)),
858    ///     "that contains x"
859    /// );
860    /// assert_eq!(
861    ///     Some(TimeWindow::from_seconds(6, 9)),
862    ///     x.intersect(&TimeWindow::from_seconds(6, 9)),
863    ///     "x contains that"
864    /// );
865    /// assert_eq!(
866    ///     Some(TimeWindow::from_seconds(6, 10)),
867    ///     x.intersect(&TimeWindow::from_seconds(6, 12))
868    /// );
869    /// assert_eq!(
870    ///     Some(TimeWindow::from_seconds(5, 9)),
871    ///     x.intersect(&TimeWindow::from_seconds(3, 9))
872    /// );
873    /// assert_eq!(
874    ///     None,
875    ///     x.intersect(&TimeWindow::from_seconds(1, 4)),
876    ///     "that is before x"
877    /// );
878    /// assert_eq!(
879    ///     Some(TimeWindow::from_seconds(5, 5)),
880    ///     x.intersect(&TimeWindow::from_seconds(1, 5)),
881    ///     "single-point intersection"
882    /// );
883    /// assert_eq!(
884    ///     Some(TimeWindow::from_seconds(10, 10)),
885    ///     x.intersect(&TimeWindow::from_seconds(10, 15)),
886    ///     "single-point intersection"
887    /// );
888    /// assert_eq!(
889    ///     None,
890    ///     x.intersect(&TimeWindow::from_seconds(11, 15)),
891    ///     "that is after x"
892    /// );
893    /// ```
894    #[must_use]
895    pub fn intersect(&self, that: &TimeWindow) -> Option<TimeWindow> {
896        let start = max(self.start, that.start);
897        let end = min(self.end, that.end);
898        (start <= end).then(|| TimeWindow::new(start, end))
899    }
900
901    /// Shifts this time window by `duration` into the future. Affects both
902    /// `start` and `end` equally, leaving the length untouched.
903    ///
904    /// # Examples
905    ///
906    /// ```
907    /// # use tinytime::TimeWindow;
908    /// # use tinytime::Duration;
909    /// # use tinytime::Time;
910    /// let mut tw = TimeWindow::new(Time::EPOCH, Time::minutes(15));
911    /// // shift to the future
912    /// tw.shift(Duration::minutes(30));
913    /// assert_eq!(TimeWindow::new(Time::minutes(30), Time::minutes(45)), tw);
914    /// // shift into the past
915    /// tw.shift(-Duration::minutes(15));
916    /// assert_eq!(TimeWindow::new(Time::minutes(15), Time::minutes(30)), tw);
917    /// ```
918    pub fn shift(&mut self, duration: Duration) {
919        self.start += duration;
920        self.end += duration;
921    }
922}
923
924impl From<TimeWindow> for (Time, Time) {
925    fn from(tw: TimeWindow) -> Self {
926        (tw.start, tw.end)
927    }
928}
929
930impl From<(Time, Time)> for TimeWindow {
931    fn from((start, end): (Time, Time)) -> Self {
932        Self { start, end }
933    }
934}
935
936/// A duration of time.
937///
938/// Duration can be negative. Internally duration is represented as
939/// milliseconds.
940#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Default, Hash)]
941#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
942#[cfg_attr(feature = "bincode", derive(::bincode::Encode, ::bincode::Decode))]
943pub struct Duration(i64);
944
945impl Duration {
946    pub const ZERO: Self = Self(0_i64);
947    pub const MAX: Self = Self(i64::MAX);
948
949    const SECOND: Duration = Duration(1000);
950    const MINUTE: Duration = Duration(60 * Self::SECOND.0);
951    const HOUR: Duration = Duration(60 * Self::MINUTE.0);
952
953    /// Create a duration instance from hours
954    #[must_use]
955    pub const fn hours(hours: i64) -> Self {
956        Duration(hours * Self::HOUR.0)
957    }
958
959    /// Create a duration instance from minutes.
960    #[must_use]
961    pub const fn minutes(minutes: i64) -> Self {
962        Duration(minutes * Self::MINUTE.0)
963    }
964
965    /// Create a duration instance from seconds.
966    #[must_use]
967    pub const fn seconds(seconds: i64) -> Self {
968        Duration(seconds * Self::SECOND.0)
969    }
970
971    /// Create a duration instance from ms.
972    #[must_use]
973    pub const fn millis(ms: i64) -> Self {
974        Duration(ms)
975    }
976
977    #[must_use]
978    pub fn abs(&self) -> Self {
979        if self >= &Duration::ZERO {
980            *self
981        } else {
982            -*self
983        }
984    }
985    /// Returns the number of whole milliseconds in the Duration instance.
986    #[must_use]
987    pub const fn as_millis(&self) -> i64 {
988        self.0
989    }
990
991    /// Returns the number of non-negative whole milliseconds in the Duration
992    /// instance.
993    #[must_use]
994    pub const fn as_millis_unsigned(&self) -> u64 {
995        as_unsigned(self.0)
996    }
997
998    /// Returns the number of whole seconds in the Duration instance.
999    #[must_use]
1000    pub const fn as_seconds(&self) -> i64 {
1001        self.0 / Self::SECOND.0
1002    }
1003
1004    /// Returns the number of non-negative whole seconds in the Duration
1005    /// instance.
1006    #[must_use]
1007    pub const fn as_seconds_unsigned(&self) -> u64 {
1008        as_unsigned(self.0 / 1000)
1009    }
1010
1011    /// Returns the number of whole minutes in the Duration instance.
1012    #[must_use]
1013    pub const fn as_minutes(&self) -> i64 {
1014        self.0 / Self::MINUTE.0
1015    }
1016
1017    /// Returns true if duration is `>= 0`.
1018    #[must_use]
1019    pub const fn is_non_negative(&self) -> bool {
1020        self.0 >= 0
1021    }
1022
1023    /// Returns true if duration is `> 0`.
1024    #[must_use]
1025    pub const fn is_positive(&self) -> bool {
1026        self.0 > 0
1027    }
1028}
1029
1030impl Deref for Duration {
1031    type Target = i64;
1032
1033    fn deref(&self) -> &Self::Target {
1034        &self.0
1035    }
1036}
1037
1038impl From<Duration> for i64 {
1039    fn from(time: Duration) -> Self {
1040        time.0
1041    }
1042}
1043
1044impl From<i64> for Duration {
1045    fn from(value: i64) -> Self {
1046        Self(value)
1047    }
1048}
1049
1050impl Neg for Duration {
1051    type Output = Self;
1052
1053    fn neg(self) -> Self::Output {
1054        Self(-self.0)
1055    }
1056}
1057
1058impl Sum for Duration {
1059    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
1060        iter.fold(Self::ZERO, Add::add)
1061    }
1062}
1063
1064impl PartialEq<std::time::Duration> for Duration {
1065    fn eq(&self, other: &std::time::Duration) -> bool {
1066        (u128::from(self.as_millis_unsigned())).eq(&other.as_millis())
1067    }
1068}
1069
1070impl PartialOrd<std::time::Duration> for Duration {
1071    fn partial_cmp(&self, other: &std::time::Duration) -> Option<Ordering> {
1072        (u128::from(self.as_millis_unsigned())).partial_cmp(&other.as_millis())
1073    }
1074}
1075
1076impl Display for Duration {
1077    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1078        if self.0 == 0 {
1079            return write!(f, "0ms");
1080        }
1081        let mut string = String::new();
1082        if self.0 < 0 {
1083            string.push('-');
1084        }
1085        let abs = self.0.abs();
1086        let ms = abs % 1000;
1087        let s = (abs / 1000) % 60;
1088        let m = (abs / 60000) % 60;
1089        let h = abs / (60 * 60 * 1000);
1090
1091        if h > 0 {
1092            string.push_str(&h.to_string());
1093            string.push('h');
1094        }
1095        if m > 0 {
1096            string.push_str(&m.to_string());
1097            string.push('m');
1098        }
1099        if s > 0 {
1100            string.push_str(&s.to_string());
1101            string.push('s');
1102        }
1103        if ms > 0 {
1104            string.push_str(&ms.to_string());
1105            string.push_str("ms");
1106        }
1107
1108        write!(f, "{string}")
1109    }
1110}
1111
1112impl Debug for Duration {
1113    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
1114        Display::fmt(self, f)
1115    }
1116}
1117
1118impl From<f64> for Duration {
1119    fn from(num: f64) -> Self {
1120        #[expect(clippy::cast_possible_truncation, reason = "expected behavior")]
1121        {
1122            Duration::millis(num.round() as i64)
1123        }
1124    }
1125}
1126
1127impl From<Duration> for f64 {
1128    fn from(num: Duration) -> Self {
1129        num.0 as f64
1130    }
1131}
1132
1133/////////////////////////////
1134// OPERATORS FOR TIME      //
1135/////////////////////////////
1136
1137impl Sub<Time> for Time {
1138    type Output = Duration;
1139
1140    fn sub(self, rhs: Time) -> Self::Output {
1141        debug_assert!(
1142            self.0.checked_sub(rhs.0).is_some(),
1143            "overflow detected: {self:?} - {rhs:?}"
1144        );
1145        Duration(self.0 - rhs.0)
1146    }
1147}
1148
1149impl Add<Duration> for Time {
1150    type Output = Time;
1151
1152    fn add(self, rhs: Duration) -> Self::Output {
1153        debug_assert!(
1154            self.0.checked_add(rhs.0).is_some(),
1155            "overflow detected: {self:?} + {rhs:?}"
1156        );
1157        Time(self.0 + rhs.0)
1158    }
1159}
1160
1161impl AddAssign<Duration> for Time {
1162    fn add_assign(&mut self, rhs: Duration) {
1163        debug_assert!(
1164            self.0.checked_add(rhs.0).is_some(),
1165            "overflow detected: {self:?} += {rhs:?}"
1166        );
1167        self.0 += rhs.0;
1168    }
1169}
1170
1171impl Sub<Duration> for Time {
1172    type Output = Time;
1173
1174    fn sub(self, rhs: Duration) -> Self::Output {
1175        debug_assert!(
1176            self.0.checked_sub(rhs.0).is_some(),
1177            "overflow detected: {self:?} - {rhs:?}"
1178        );
1179        Time(self.0 - rhs.0)
1180    }
1181}
1182
1183impl SubAssign<Duration> for Time {
1184    fn sub_assign(&mut self, rhs: Duration) {
1185        debug_assert!(
1186            self.0.checked_sub(rhs.0).is_some(),
1187            "overflow detected: {self:?} -= {rhs:?}"
1188        );
1189        self.0 -= rhs.0;
1190    }
1191}
1192
1193/////////////////////////////
1194// OPERATORS FOR DURATION  //
1195/////////////////////////////
1196
1197impl Add<Duration> for Duration {
1198    type Output = Duration;
1199
1200    fn add(self, rhs: Duration) -> Self::Output {
1201        debug_assert!(
1202            self.0.checked_add(rhs.0).is_some(),
1203            "overflow detected: {self:?} + {rhs:?}"
1204        );
1205        Duration(self.0 + rhs.0)
1206    }
1207}
1208
1209impl AddAssign<Duration> for Duration {
1210    fn add_assign(&mut self, rhs: Duration) {
1211        debug_assert!(
1212            self.0.checked_add(rhs.0).is_some(),
1213            "overflow detected: {self:?} += {rhs:?}"
1214        );
1215        self.0 += rhs.0;
1216    }
1217}
1218
1219impl Sub<Duration> for Duration {
1220    type Output = Duration;
1221
1222    fn sub(self, rhs: Duration) -> Self::Output {
1223        debug_assert!(
1224            self.0.checked_sub(rhs.0).is_some(),
1225            "overflow detected: {self:?} - {rhs:?}"
1226        );
1227        Duration(self.0 - rhs.0)
1228    }
1229}
1230
1231impl SubAssign<Duration> for Duration {
1232    fn sub_assign(&mut self, rhs: Duration) {
1233        debug_assert!(
1234            self.0.checked_sub(rhs.0).is_some(),
1235            "overflow detected: {self:?} -= {rhs:?}"
1236        );
1237        self.0 -= rhs.0;
1238    }
1239}
1240
1241impl Mul<f64> for Duration {
1242    type Output = Duration;
1243
1244    fn mul(self, rhs: f64) -> Self::Output {
1245        #[expect(clippy::cast_possible_truncation, reason = "expected behavior")]
1246        {
1247            Duration((self.0 as f64 * rhs).round() as i64)
1248        }
1249    }
1250}
1251
1252impl Mul<Duration> for f64 {
1253    type Output = Duration;
1254
1255    fn mul(self, rhs: Duration) -> Self::Output {
1256        rhs * self
1257    }
1258}
1259
1260impl MulAssign<f64> for Duration {
1261    fn mul_assign(&mut self, rhs: f64) {
1262        #[expect(clippy::cast_possible_truncation, reason = "expected behavior")]
1263        {
1264            self.0 = (self.0 as f64 * rhs).round() as i64;
1265        }
1266    }
1267}
1268
1269// Returns rounded Duration
1270impl Div<f64> for Duration {
1271    type Output = Duration;
1272
1273    fn div(self, rhs: f64) -> Self::Output {
1274        debug_assert!(
1275            rhs.abs() > f64::EPSILON,
1276            "Dividing by zero results in INF. This is probably not what you want."
1277        );
1278        #[expect(clippy::cast_possible_truncation, reason = "expected behavior")]
1279        {
1280            Duration((self.0 as f64 / rhs).round() as i64)
1281        }
1282    }
1283}
1284
1285impl DivAssign<f64> for Duration {
1286    fn div_assign(&mut self, rhs: f64) {
1287        #[expect(clippy::cast_possible_truncation, reason = "expected behavior")]
1288        {
1289            self.0 = (self.0 as f64 / rhs).round() as i64;
1290        }
1291    }
1292}
1293
1294impl Mul<i64> for Duration {
1295    type Output = Duration;
1296
1297    fn mul(self, rhs: i64) -> Self::Output {
1298        debug_assert!(
1299            self.0.checked_mul(rhs).is_some(),
1300            "overflow detected: {self:?} * {rhs:?}"
1301        );
1302        Duration(self.0 * rhs)
1303    }
1304}
1305
1306impl Mul<Duration> for i64 {
1307    type Output = Duration;
1308
1309    fn mul(self, rhs: Duration) -> Self::Output {
1310        rhs * self
1311    }
1312}
1313
1314impl MulAssign<i64> for Duration {
1315    fn mul_assign(&mut self, rhs: i64) {
1316        debug_assert!(
1317            self.0.checked_mul(rhs).is_some(),
1318            "overflow detected: {self:?} *= {rhs:?}"
1319        );
1320        self.0 *= rhs;
1321    }
1322}
1323
1324impl Div<i64> for Duration {
1325    type Output = Duration;
1326
1327    fn div(self, rhs: i64) -> Self::Output {
1328        // forward to the float implementation
1329        self / rhs as f64
1330    }
1331}
1332
1333impl DivAssign<i64> for Duration {
1334    fn div_assign(&mut self, rhs: i64) {
1335        // forward to the float implementation
1336        self.div_assign(rhs as f64);
1337    }
1338}
1339
1340impl Div<Duration> for Duration {
1341    type Output = f64;
1342
1343    fn div(self, rhs: Duration) -> Self::Output {
1344        debug_assert_ne!(
1345            rhs,
1346            Duration::ZERO,
1347            "Dividing by zero results in INF. This is probably not what you want."
1348        );
1349        self.0 as f64 / rhs.0 as f64
1350    }
1351}
1352
1353impl Rem<Duration> for Time {
1354    type Output = Duration;
1355
1356    fn rem(self, rhs: Duration) -> Self::Output {
1357        Duration(self.0 % rhs.0)
1358    }
1359}
1360
1361impl Rem<Duration> for Duration {
1362    type Output = Duration;
1363
1364    fn rem(self, rhs: Duration) -> Self::Output {
1365        Duration(self.0 % rhs.0)
1366    }
1367}
1368
1369impl RemAssign<Duration> for Duration {
1370    fn rem_assign(&mut self, rhs: Duration) {
1371        self.0 %= rhs.0;
1372    }
1373}
1374
1375impl From<Duration> for std::time::Duration {
1376    fn from(input: Duration) -> Self {
1377        debug_assert!(
1378            input.is_non_negative(),
1379            "Negative Duration {input} cannot be converted to std::time::Duration"
1380        );
1381        #[expect(clippy::cast_sign_loss, reason = "caught by the debug_assert above")]
1382        let secs = (input.0 / 1000) as u64;
1383        #[expect(
1384            clippy::cast_possible_truncation,
1385            clippy::cast_sign_loss,
1386            reason = "casting to u32 is safe here because it is guaranteed that the value is in 0..1_000_000_000. The sign loss is caught by the debug_assert above."
1387        )]
1388        let nanos = ((input.0 % 1000) * 1_000_000) as u32;
1389        std::time::Duration::new(secs, nanos)
1390    }
1391}
1392
1393impl From<std::time::Duration> for Duration {
1394    fn from(input: std::time::Duration) -> Self {
1395        debug_assert!(
1396            i64::try_from(input.as_millis()).is_ok(),
1397            "Input std::time::Duration ({input:?}) is too large to be converted to tinytime::Duration"
1398        );
1399        #[expect(clippy::cast_possible_truncation, reason = "expected behavior")]
1400        Duration::millis(input.as_millis() as i64)
1401    }
1402}
1403
1404/// Parses Duration from str
1405///
1406/// # Example
1407/// ```
1408/// # use tinytime::Duration;
1409/// # use std::str::FromStr;
1410/// assert_eq!(Duration::millis(2), Duration::from_str("2ms").unwrap());
1411/// assert_eq!(Duration::seconds(3), Duration::from_str("3s").unwrap());
1412/// assert_eq!(Duration::minutes(4), Duration::from_str("4m").unwrap());
1413/// assert_eq!(Duration::hours(5), Duration::from_str("5h").unwrap());
1414///
1415/// assert_eq!(
1416///     Duration::hours(5) + Duration::minutes(2),
1417///     Duration::from_str("5h2m").unwrap()
1418/// );
1419/// assert_eq!(
1420///     Duration::hours(5) + Duration::minutes(2) + Duration::millis(1123),
1421///     Duration::from_str("5h2m1s123ms").unwrap()
1422/// );
1423/// assert_eq!(
1424///     Duration::seconds(5) - Duration::minutes(2),
1425///     Duration::from_str("-1m55s").unwrap()
1426/// );
1427/// ```
1428impl FromStr for Duration {
1429    type Err = DurationParseError;
1430
1431    #[expect(
1432        clippy::string_slice,
1433        reason = "all slice indices come from methods that guarantee correctness"
1434    )]
1435    fn from_str(mut s: &str) -> Result<Self, Self::Err> {
1436        let without_sign = s.strip_prefix('-');
1437        let negative = without_sign.is_some();
1438        s = without_sign.unwrap_or(s);
1439
1440        let mut duration = Self::ZERO;
1441        while !s.is_empty() {
1442            let without_number = s.trim_start_matches(|c: char| c.is_ascii_digit());
1443            let Ok(number) = s[..s.len() - without_number.len()].parse::<i64>() else {
1444                return Err(DurationParseError::UnrecognizedFormat);
1445            };
1446            let without_unit = without_number.trim_start_matches(|c: char| !c.is_ascii_digit());
1447            let unit = &without_number[..without_number.len() - without_unit.len()];
1448
1449            duration += match unit {
1450                "h" => Duration::hours(number),
1451                "m" => Duration::minutes(number),
1452                "s" => Duration::seconds(number),
1453                "ms" => Duration::millis(number),
1454                _ => return Err(DurationParseError::UnrecognizedFormat),
1455            };
1456            s = without_unit;
1457        }
1458
1459        if negative {
1460            duration = -duration;
1461        }
1462
1463        Ok(duration)
1464    }
1465}
1466
1467#[derive(Debug, Clone, Copy)]
1468pub enum DurationParseError {
1469    UnrecognizedFormat,
1470}
1471
1472impl Error for DurationParseError {}
1473
1474impl Display for DurationParseError {
1475    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1476        write!(
1477            f,
1478            "Unrecognized Duration format, valid examples are '2h3s', '1m', '1h3m5s700ms'"
1479        )
1480    }
1481}
1482
1483/// Work-around for `max` in `std` not being const
1484const fn as_unsigned(x: i64) -> u64 {
1485    if x >= 0 { x as u64 } else { 0 }
1486}
1487
1488#[cfg(test)]
1489mod time_test {
1490    use crate::Duration;
1491    use crate::Time;
1492
1493    #[test]
1494    fn test_display() {
1495        struct TestCase {
1496            name: &'static str,
1497            input: Time,
1498            expected: String,
1499        }
1500        let tests = vec![
1501            TestCase {
1502                name: "EPOCH",
1503                input: Time::EPOCH,
1504                expected: "1970-01-01T00:00:00+00:00".to_string(),
1505            },
1506            TestCase {
1507                name: "i16::MAX + 1",
1508                input: Time::seconds(i64::from(i16::MAX) + 1),
1509                expected: "1970-01-01T09:06:08+00:00".to_string(),
1510            },
1511            TestCase {
1512                name: "i32::MAX + 1",
1513                input: Time::seconds(i64::from(i32::MAX) + 1),
1514                expected: "2038-01-19T03:14:08+00:00".to_string(),
1515            },
1516            TestCase {
1517                name: "u32::MAX + 1",
1518                input: Time::seconds(i64::from(u32::MAX) + 1),
1519                expected: "2106-02-07T06:28:16+00:00".to_string(),
1520            },
1521            TestCase {
1522                name: "very large",
1523                input: Time::seconds(i64::from(i32::MAX) * 3500),
1524                expected: "+240148-08-31T19:28:20+00:00".to_string(),
1525            },
1526            TestCase {
1527                name: "MAX",
1528                input: Time::MAX,
1529                expected: "∞".to_string(),
1530            },
1531            TestCase {
1532                name: "i16::MIN",
1533                input: Time::seconds(i64::from(i16::MIN)),
1534                expected: "1969-12-31T14:53:52+00:00".to_string(),
1535            },
1536            TestCase {
1537                name: "i64::MIN",
1538                input: Time::millis(i64::MIN),
1539                expected: "∞".to_string(),
1540            },
1541        ];
1542        for test in tests {
1543            assert_eq!(
1544                test.expected,
1545                test.input.to_rfc3339(),
1546                "to_rfc3339 failed for test '{}'",
1547                test.name
1548            );
1549            assert_eq!(
1550                test.expected,
1551                test.input.format("%Y-%m-%dT%H:%M:%S+00:00").to_string(),
1552                "format failed for test '{}'",
1553                test.name
1554            );
1555        }
1556    }
1557
1558    #[test]
1559    fn test_debug() {
1560        struct TestCase {
1561            name: &'static str,
1562            input: Time,
1563            expected: String,
1564        }
1565        let tests = vec![
1566            TestCase {
1567                name: "EPOCH",
1568                input: Time::EPOCH,
1569                expected: "00:00:00".to_string(),
1570            },
1571            TestCase {
1572                name: "i16::MAX + 1",
1573                input: Time::seconds(i64::from(i16::MAX) + 1),
1574                expected: "09:06:08".to_string(),
1575            },
1576            TestCase {
1577                name: "i32::MAX + 1",
1578                input: Time::seconds(i64::from(i32::MAX) + 1),
1579                expected: "596523:14:08".to_string(),
1580            },
1581            TestCase {
1582                name: "u32::MAX + 1",
1583                input: Time::seconds(i64::from(u32::MAX) + 1),
1584                expected: "1193046:28:16".to_string(),
1585            },
1586            TestCase {
1587                name: "very large",
1588                input: Time::seconds(i64::from(i32::MAX) * 3500),
1589                expected: "2087831323:28:20".to_string(),
1590            },
1591            TestCase {
1592                name: "MAX",
1593                input: Time::MAX,
1594                expected: "2562047788015:12:55.807".to_string(),
1595            },
1596            TestCase {
1597                name: "i16::MIN",
1598                input: Time::seconds(i64::from(i16::MIN)),
1599                expected: "-09:06:08".to_string(),
1600            },
1601            TestCase {
1602                name: "i64::MIN",
1603                input: Time::millis(i64::MIN),
1604                expected: "-2562047788015:12:55.808".to_string(),
1605            },
1606            TestCase {
1607                name: "millis",
1608                input: Time::hours(3) + Duration::millis(42),
1609                expected: "03:00:00.042".to_string(),
1610            },
1611        ];
1612        for test in tests {
1613            assert_eq!(
1614                test.expected,
1615                format!("{:?}", test.input),
1616                "test '{}' failed",
1617                test.name
1618            );
1619        }
1620    }
1621
1622    #[test]
1623    fn test_time_since_epoch() {
1624        let expected = Duration::seconds(3);
1625        let actual = Time::seconds(3).since_epoch();
1626        assert_eq!(expected, actual);
1627    }
1628
1629    #[test]
1630    fn test_time_from_duration() {
1631        let duration_pos = Duration::seconds(3);
1632        assert_eq!(Ok(Time::seconds(3)), Time::try_from(duration_pos));
1633
1634        let duration_neg = Duration::seconds(-3);
1635        assert_eq!(
1636            Err("Duration cannot be negative."),
1637            Time::try_from(duration_neg)
1638        );
1639    }
1640}
1641
1642#[cfg(test)]
1643mod duration_test {
1644    use super::*;
1645
1646    #[test]
1647    fn duration_display() {
1648        assert_eq!("1ms", Duration::millis(1).to_string());
1649        assert_eq!("2s", Duration::seconds(2).to_string());
1650        assert_eq!("3m", Duration::minutes(3).to_string());
1651        assert_eq!("4h", Duration::hours(4).to_string());
1652
1653        assert_eq!("1m1s", Duration::seconds(61).to_string());
1654        assert_eq!(
1655            "2h3m4s5ms",
1656            (Duration::hours(2)
1657                + Duration::minutes(3)
1658                + Duration::seconds(4)
1659                + Duration::millis(5))
1660            .to_string()
1661        );
1662
1663        assert_eq!("0ms", Duration::ZERO.to_string());
1664        assert_eq!("-1m1s", Duration::seconds(-61).to_string());
1665    }
1666
1667    #[test]
1668    fn test_time_window_display() {
1669        assert_eq!(
1670            "[1970-01-01T00:00:00+00:00, ∞]",
1671            TimeWindow::new(Time::EPOCH, Time::MAX).to_string()
1672        );
1673        assert_eq!(
1674            "[1970-01-01T01:00:00+00:00, 2024-02-06T16:53:47+00:00]",
1675            TimeWindow::new(Time::hours(1), Time::millis(1_707_238_427_962)).to_string()
1676        );
1677    }
1678
1679    #[test]
1680    fn test_duration_is_non_negative_returns_correctly() {
1681        struct TestCase {
1682            name: &'static str,
1683            input: i64,
1684            expected: bool,
1685        }
1686
1687        let tests = vec![
1688            TestCase {
1689                name: "negative",
1690                input: -1,
1691                expected: false,
1692            },
1693            TestCase {
1694                name: "zero",
1695                input: 0,
1696                expected: true,
1697            },
1698            TestCase {
1699                name: "positive",
1700                input: 1,
1701                expected: true,
1702            },
1703        ];
1704
1705        for t in tests {
1706            let actual = Duration(t.input).is_non_negative();
1707            assert_eq!(t.expected, actual, "failed '{}'", t.name);
1708        }
1709    }
1710
1711    #[test]
1712    fn test_duration_abs_removes_sign() {
1713        struct TestCase {
1714            name: &'static str,
1715            input: Duration,
1716            expected: Duration,
1717        }
1718
1719        let tests = vec![
1720            TestCase {
1721                name: "negative",
1722                input: Duration::hours(-1),
1723                expected: Duration::hours(1),
1724            },
1725            TestCase {
1726                name: "zero",
1727                input: Duration::ZERO,
1728                expected: Duration::ZERO,
1729            },
1730            TestCase {
1731                name: "positive",
1732                input: Duration::minutes(1),
1733                expected: Duration::minutes(1),
1734            },
1735        ];
1736
1737        for t in tests {
1738            let actual = t.input.abs();
1739            assert_eq!(t.expected, actual, "failed '{}'", t.name);
1740        }
1741    }
1742
1743    #[test]
1744    fn test_duration_is_positive_returns_correctly() {
1745        struct TestCase {
1746            name: &'static str,
1747            input: i64,
1748            expected: bool,
1749        }
1750
1751        let tests = vec![
1752            TestCase {
1753                name: "negative",
1754                input: -1,
1755                expected: false,
1756            },
1757            TestCase {
1758                name: "zero",
1759                input: 0,
1760                expected: false,
1761            },
1762            TestCase {
1763                name: "positive",
1764                input: 1,
1765                expected: true,
1766            },
1767        ];
1768
1769        for t in tests {
1770            let actual = Duration(t.input).is_positive();
1771            assert_eq!(t.expected, actual, "failed '{}'", t.name);
1772        }
1773    }
1774
1775    #[test]
1776    fn time_add_duration() {
1777        let mut time = Time::millis(1);
1778        let expected_time = Time::millis(3);
1779        let duration = Duration::millis(2);
1780        //  add
1781        assert_eq!(expected_time, time + duration);
1782        // add assign
1783        time += duration;
1784        assert_eq!(expected_time, time);
1785    }
1786
1787    #[test]
1788    fn time_sub_duration() {
1789        let mut time = Time::millis(10);
1790        let expected_time = Time::millis(3);
1791        let duration = Duration::millis(7);
1792        // small time: sub
1793        assert_eq!(expected_time, time - duration);
1794        // small time: sub assign
1795        time -= duration;
1796        assert_eq!(expected_time, time);
1797    }
1798
1799    #[test]
1800    fn time_sub_time() {
1801        // small numbers
1802        let time = Time::minutes(7);
1803        let time2 = Time::minutes(3);
1804        assert_eq!(Duration::minutes(4), time - time2);
1805        assert_eq!(Duration::minutes(-4), time2 - time);
1806    }
1807
1808    #[test]
1809    fn time_rem_duration() {
1810        let time57 = Time::minutes(57);
1811        assert_eq!(Duration::ZERO, time57 % Duration::minutes(1));
1812        assert_eq!(Duration::minutes(57), time57 % Duration::minutes(60));
1813        assert_eq!(
1814            Duration::minutes(57),
1815            (time57 + Duration::hours(17)) % Duration::minutes(60)
1816        );
1817    }
1818
1819    #[test]
1820    fn duration_rem_duration() {
1821        let dur34 = Duration::minutes(34);
1822        assert_eq!(Duration::ZERO, dur34 % Duration::minutes(1));
1823        assert_eq!(Duration::minutes(34), dur34 % Duration::minutes(45));
1824        assert_eq!(Duration::minutes(10), dur34 % Duration::minutes(12));
1825    }
1826
1827    #[test]
1828    fn duration_rem_assign_duration() {
1829        let mut dur = Duration::minutes(734);
1830        dur %= Duration::minutes(100);
1831        assert_eq!(Duration::minutes(34), dur);
1832    }
1833}