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}