1#[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#[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    #[must_use]
109    pub fn now() -> Time {
110        Time::from(SystemTime::now())
111    }
112
113    #[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    #[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    #[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    #[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    #[must_use]
210    pub fn checked_sub(&self, rhs: Duration) -> Option<Self> {
211        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        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#[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    #[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    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    #[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    #[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    #[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    #[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    #[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    #[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    #[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    #[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    #[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    #[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    #[must_use]
634    pub fn postpone_start_shrink_to(&self, new_length: Duration) -> Self {
635        let length = new_length
636            .min(self.length()) .max(Duration::ZERO); self.with_start(self.end - length)
639    }
640
641    #[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    #[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    #[must_use]
725    pub fn prepone_end_shrink_to(&self, new_length: Duration) -> Self {
726        let length = new_length
727            .min(self.length()) .max(Duration::ZERO); self.with_end(self.start + length)
730    }
731
732    #[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    #[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    #[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    #[must_use]
818    pub fn contains(&self, that: Time) -> bool {
819        self.start <= that && that <= self.end
820    }
821
822    #[must_use]
839    pub fn overlaps(&self, that: &TimeWindow) -> bool {
840        self.start < that.end && that.start < self.end
841    }
842
843    #[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    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#[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    #[must_use]
955    pub const fn hours(hours: i64) -> Self {
956        Duration(hours * Self::HOUR.0)
957    }
958
959    #[must_use]
961    pub const fn minutes(minutes: i64) -> Self {
962        Duration(minutes * Self::MINUTE.0)
963    }
964
965    #[must_use]
967    pub const fn seconds(seconds: i64) -> Self {
968        Duration(seconds * Self::SECOND.0)
969    }
970
971    #[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    #[must_use]
987    pub const fn as_millis(&self) -> i64 {
988        self.0
989    }
990
991    #[must_use]
994    pub const fn as_millis_unsigned(&self) -> u64 {
995        as_unsigned(self.0)
996    }
997
998    #[must_use]
1000    pub const fn as_seconds(&self) -> i64 {
1001        self.0 / Self::SECOND.0
1002    }
1003
1004    #[must_use]
1007    pub const fn as_seconds_unsigned(&self) -> u64 {
1008        as_unsigned(self.0 / 1000)
1009    }
1010
1011    #[must_use]
1013    pub const fn as_minutes(&self) -> i64 {
1014        self.0 / Self::MINUTE.0
1015    }
1016
1017    #[must_use]
1019    pub const fn is_non_negative(&self) -> bool {
1020        self.0 >= 0
1021    }
1022
1023    #[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
1133impl 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
1193impl 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
1269impl 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        self / rhs as f64
1330    }
1331}
1332
1333impl DivAssign<i64> for Duration {
1334    fn div_assign(&mut self, rhs: i64) {
1335        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
1404impl 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
1483const 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        assert_eq!(expected_time, time + duration);
1782        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        assert_eq!(expected_time, time - duration);
1794        time -= duration;
1796        assert_eq!(expected_time, time);
1797    }
1798
1799    #[test]
1800    fn time_sub_time() {
1801        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}