1use core::iter::Sum;
4use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
5
6use crate::consts::{NANOS_PER_MICROSECOND, NANOS_PER_MILLISECOND, NANOS_PER_SECOND};
7use crate::{
8 BootUptime, RawUptime, TaiTimestamp, Tickdelta, Tickstamp, TimePoint, Timedelta, Timestamp,
9 Uptime,
10};
11
12#[inline]
14const fn floor(value: i64, unit: i64) -> i64 {
15 match value.checked_rem_euclid(unit) {
16 Some(remainder) => value.saturating_sub(remainder),
17 None => value,
18 }
19}
20
21#[inline]
23const fn ceil(value: i64, unit: i64) -> i64 {
24 match value.checked_rem_euclid(unit) {
25 Some(0) | None => value,
26 Some(remainder) => {
27 value.saturating_sub(remainder).saturating_add_unsigned(unit.unsigned_abs())
28 },
29 }
30}
31
32macro_rules! wrapped {
35 ($count:expr, $wrap:path) => {
36 match $count {
37 Some(count) => Some($wrap(count)),
38 None => None,
39 }
40 };
41}
42
43macro_rules! span {
45 ($span:ident) => {
46 const _: () = assert!(size_of::<$span>() == size_of::<i64>(), "an `i64`, and nothing else");
47
48 impl $span {
49 pub const ZERO: Self = Self(0);
51 pub const MIN: Self = Self(i64::MIN);
53 pub const MAX: Self = Self(i64::MAX);
55
56 #[inline]
58 #[must_use]
59 pub const fn is_zero(self) -> bool {
60 self.0 == 0
61 }
62
63 #[inline]
65 #[must_use]
66 pub const fn is_negative(self) -> bool {
67 self.0 < 0
68 }
69
70 #[inline]
72 #[must_use]
73 pub const fn is_positive(self) -> bool {
74 self.0 > 0
75 }
76
77 #[inline]
79 #[must_use]
80 pub const fn abs(self) -> Self {
81 Self(self.0.saturating_abs())
82 }
83
84 #[inline]
86 #[must_use]
87 pub const fn checked_add(self, other: Self) -> Option<Self> {
88 wrapped!(self.0.checked_add(other.0), Self)
89 }
90
91 #[inline]
93 #[must_use]
94 pub const fn checked_sub(self, other: Self) -> Option<Self> {
95 wrapped!(self.0.checked_sub(other.0), Self)
96 }
97
98 #[inline]
100 #[must_use]
101 pub const fn checked_mul(self, factor: i64) -> Option<Self> {
102 wrapped!(self.0.checked_mul(factor), Self)
103 }
104
105 #[inline]
108 #[must_use]
109 pub const fn checked_div(self, parts: i64) -> Option<Self> {
110 wrapped!(self.0.checked_div(parts), Self)
111 }
112
113 #[inline]
115 #[must_use]
116 pub const fn checked_neg(self) -> Option<Self> {
117 wrapped!(self.0.checked_neg(), Self)
118 }
119
120 #[inline]
122 #[must_use]
123 pub const fn checked_abs(self) -> Option<Self> {
124 wrapped!(self.0.checked_abs(), Self)
125 }
126
127 #[inline]
129 #[must_use]
130 pub const fn floor(self, unit: Self) -> Self {
131 Self(floor(self.0, unit.0))
132 }
133
134 #[inline]
136 #[must_use]
137 pub const fn ceil(self, unit: Self) -> Self {
138 Self(ceil(self.0, unit.0))
139 }
140 }
141
142 impl Add for $span {
144 type Output = Self;
145
146 #[inline]
147 fn add(self, other: Self) -> Self {
148 Self(self.0.saturating_add(other.0))
149 }
150 }
151
152 impl AddAssign for $span {
154 #[inline]
155 fn add_assign(&mut self, other: Self) {
156 *self = *self + other;
157 }
158 }
159
160 impl Sub for $span {
162 type Output = Self;
163
164 #[inline]
165 fn sub(self, other: Self) -> Self {
166 Self(self.0.saturating_sub(other.0))
167 }
168 }
169
170 impl SubAssign for $span {
172 #[inline]
173 fn sub_assign(&mut self, other: Self) {
174 *self = *self - other;
175 }
176 }
177
178 impl Neg for $span {
180 type Output = Self;
181
182 #[inline]
183 fn neg(self) -> Self {
184 Self(self.0.saturating_neg())
185 }
186 }
187
188 impl Mul<i64> for $span {
190 type Output = Self;
191
192 #[inline]
193 fn mul(self, factor: i64) -> Self {
194 Self(self.0.saturating_mul(factor))
195 }
196 }
197
198 impl Mul<$span> for i64 {
200 type Output = $span;
201
202 #[inline]
203 fn mul(self, span: $span) -> $span {
204 span * self
205 }
206 }
207
208 impl MulAssign<i64> for $span {
210 #[inline]
211 fn mul_assign(&mut self, factor: i64) {
212 *self = *self * factor;
213 }
214 }
215
216 impl Sum for $span {
218 #[inline]
219 fn sum<I: Iterator<Item = Self>>(spans: I) -> Self {
220 spans.fold(Self::ZERO, Add::add)
221 }
222 }
223
224 impl<'a> Sum<&'a Self> for $span {
226 #[inline]
227 fn sum<I: Iterator<Item = &'a Self>>(spans: I) -> Self {
228 spans.copied().sum()
229 }
230 }
231 };
232}
233
234macro_rules! point {
236 ($point:ident, $span:ident) => {
237 const _: () =
238 assert!(size_of::<$point>() == size_of::<i64>(), "an `i64`, and nothing else");
239
240 impl $point {
241 pub const MIN: Self = Self(i64::MIN);
243 pub const MAX: Self = Self(i64::MAX);
245
246 #[inline]
248 #[must_use]
249 pub const fn checked_add(self, span: $span) -> Option<Self> {
250 wrapped!(self.0.checked_add(span.0), Self)
251 }
252
253 #[inline]
255 #[must_use]
256 pub const fn checked_sub(self, span: $span) -> Option<Self> {
257 wrapped!(self.0.checked_sub(span.0), Self)
258 }
259
260 #[inline]
262 #[must_use]
263 pub const fn checked_since(self, start: Self) -> Option<$span> {
264 wrapped!(self.0.checked_sub(start.0), $span)
265 }
266
267 #[inline]
270 #[must_use]
271 pub const fn floor(self, unit: $span) -> Self {
272 Self(floor(self.0, unit.0))
273 }
274
275 #[inline]
278 #[must_use]
279 pub const fn ceil(self, unit: $span) -> Self {
280 Self(ceil(self.0, unit.0))
281 }
282 }
283
284 impl Add<$span> for $point {
286 type Output = Self;
287
288 #[inline]
289 fn add(self, span: $span) -> Self {
290 Self(self.0.saturating_add(span.0))
291 }
292 }
293
294 impl AddAssign<$span> for $point {
296 #[inline]
297 fn add_assign(&mut self, span: $span) {
298 *self = *self + span;
299 }
300 }
301
302 impl Sub<$span> for $point {
304 type Output = Self;
305
306 #[inline]
307 fn sub(self, span: $span) -> Self {
308 Self(self.0.saturating_sub(span.0))
309 }
310 }
311
312 impl SubAssign<$span> for $point {
314 #[inline]
315 fn sub_assign(&mut self, span: $span) {
316 *self = *self - span;
317 }
318 }
319
320 impl Sub for $point {
322 type Output = $span;
323
324 #[inline]
325 fn sub(self, start: Self) -> $span {
326 $span(self.0.saturating_sub(start.0))
327 }
328 }
329
330 impl TimePoint for $point {
331 type Span = $span;
332
333 #[inline]
334 fn from_count(count: i64) -> Self {
335 Self(count)
336 }
337
338 #[inline]
339 fn count(self) -> i64 {
340 self.0
341 }
342 }
343 };
344}
345
346macro_rules! nanosecond_units {
348 ($point:ident, $origin:literal) => {
349 impl $point {
350 #[doc = concat!("The point `nanos` nanoseconds after ", $origin, ".")]
351 #[inline]
352 #[must_use]
353 pub const fn from_nanos(nanos: i64) -> Self {
354 Self(nanos)
355 }
356
357 #[doc = concat!("The point `micros` microseconds after ", $origin, ", saturating.")]
358 #[inline]
359 #[must_use]
360 pub const fn from_micros(micros: i64) -> Self {
361 Self(micros.saturating_mul(NANOS_PER_MICROSECOND))
362 }
363
364 #[doc = concat!("The point `millis` milliseconds after ", $origin, ", saturating.")]
365 #[inline]
366 #[must_use]
367 pub const fn from_millis(millis: i64) -> Self {
368 Self(millis.saturating_mul(NANOS_PER_MILLISECOND))
369 }
370
371 #[doc = concat!("The point `secs` seconds after ", $origin, ", saturating.")]
372 #[inline]
373 #[must_use]
374 pub const fn from_secs(secs: i64) -> Self {
375 Self(secs.saturating_mul(NANOS_PER_SECOND))
376 }
377
378 #[doc = concat!("Nanoseconds since ", $origin, ".")]
379 #[inline]
380 #[must_use]
381 pub const fn as_nanos(self) -> i64 {
382 self.0
383 }
384
385 #[doc = concat!("Whole microseconds since ", $origin, ", rounded down.")]
386 #[inline]
387 #[must_use]
388 pub const fn as_micros(self) -> i64 {
389 self.0.div_euclid(NANOS_PER_MICROSECOND)
390 }
391
392 #[doc = concat!("Whole milliseconds since ", $origin, ", rounded down.")]
393 #[inline]
394 #[must_use]
395 pub const fn as_millis(self) -> i64 {
396 self.0.div_euclid(NANOS_PER_MILLISECOND)
397 }
398
399 #[doc = concat!("Whole seconds since ", $origin, ", rounded down.")]
400 #[inline]
401 #[must_use]
402 pub const fn as_secs(self) -> i64 {
403 self.0.div_euclid(NANOS_PER_SECOND)
404 }
405 }
406 };
407}
408
409span!(Timedelta);
410span!(Tickdelta);
411
412point!(Timestamp, Timedelta);
413point!(TaiTimestamp, Timedelta);
414point!(Uptime, Timedelta);
415point!(RawUptime, Timedelta);
416point!(BootUptime, Timedelta);
417point!(Tickstamp, Tickdelta);
418
419nanosecond_units!(Timestamp, "the Unix epoch");
420nanosecond_units!(TaiTimestamp, "1970-01-01T00:00:00 TAI");
421nanosecond_units!(Uptime, "the monotonic clock's origin");
422nanosecond_units!(RawUptime, "the raw monotonic clock's origin");
423nanosecond_units!(BootUptime, "boot");
424
425#[cfg(test)]
426mod tests {
427 use core::hint::black_box;
428
429 use proptest::prelude::{prop_assert, prop_assert_eq, prop_assume, proptest};
430
431 use crate::{Tickdelta, Tickstamp, Timedelta, Timestamp, Uptime};
432
433 #[test]
434 fn floor_and_ceil_tile_the_line_on_both_sides_of_zero() {
435 let unit = Timedelta::MICROSECOND;
436 let (after, before) = (Timestamp::from_nanos(10_500), Timestamp::from_nanos(-10_500));
437 assert_eq!(after.floor(unit), Timestamp::from_nanos(10_000), "down, after the epoch");
438 assert_eq!(after.ceil(unit), Timestamp::from_nanos(11_000), "and up");
439 assert_eq!(before.floor(unit), Timestamp::from_nanos(-11_000), "down, before it");
440 assert_eq!(before.ceil(unit), Timestamp::from_nanos(-10_000), "and up");
441 let multiple = Timestamp::from_nanos(11_000);
442 assert_eq!(multiple.ceil(unit), multiple, "a multiple stays where it is");
443 }
444
445 #[test]
446 fn floor_and_ceil_take_the_units_magnitude_and_pass_over_a_zero_unit() {
447 let instant = Timestamp::from_nanos(10_500);
448 let backwards = -Timedelta::MICROSECOND;
449 assert_eq!(instant.floor(backwards), Timestamp::from_nanos(10_000), "a backwards unit");
450 assert_eq!(instant.floor(Timedelta::ZERO), instant, "no unit");
451 let longest = Timestamp::from_nanos(-5).ceil(Timedelta::MIN);
452 assert_eq!(longest, Timestamp::UNIX_EPOCH, "the longest unit, whose magnitude is no `i64`");
453 }
454
455 #[test]
456 fn point_arithmetic_saturates_and_its_checked_twin_refuses() {
457 let span = Timedelta::NANOSECOND;
458 assert_eq!(Timestamp::MAX + span, Timestamp::MAX, "a point past the end saturates");
459 assert_eq!(Timestamp::MIN - span, Timestamp::MIN, "at both ends");
460 assert_eq!(Timestamp::MAX.checked_add(span), None, "and its checked twin refuses");
461 assert_eq!(Timestamp::MIN.checked_sub(span), None, "at both ends");
462 let widest = Timestamp::MAX - Timestamp::MIN;
463 assert_eq!(widest, Timedelta::MAX, "a span past its range saturates");
464 assert_eq!(Timestamp::MAX.checked_since(Timestamp::MIN), None, "or is refused");
465 let (start, end) = (Tickstamp::from_ticks(1_000), Tickstamp::from_ticks(1_025));
466 assert_eq!(end - start, Tickdelta::from_ticks(25), "a later reading minus an earlier");
467 assert_eq!(start - end, Tickdelta::from_ticks(-25), "and the other way about");
468 }
469
470 #[test]
471 fn span_arithmetic_saturates_and_its_checked_twin_refuses() {
472 let ten = Timedelta::from_nanos(10);
473 assert_eq!(-ten + ten * 3, Timedelta::from_nanos(20), "negation, sum and product");
474 assert_eq!(3 * ten, ten * 3, "a factor on either side");
475 assert_eq!([ten, ten, ten].iter().sum::<Timedelta>(), ten * 3, "and a sum of many");
476 assert_eq!(Timedelta::MAX + ten, Timedelta::MAX, "past the end saturates");
477 assert_eq!(-Timedelta::MIN, Timedelta::MAX, "so does the negation with no twin");
478 assert_eq!(Timedelta::MIN.abs(), Timedelta::MAX, "and the magnitude");
479 assert_eq!(Timedelta::MIN.checked_abs(), None, "which the checked twins refuse");
480 assert_eq!(Timedelta::MIN.checked_neg(), None, "both");
481 assert_eq!(ten.checked_div(0), None, "no parts");
482 assert_eq!(Timedelta::MIN.checked_div(-1), None, "a quotient past the range");
483 let part = Timedelta::from_nanos(-7).checked_div(2);
484 assert_eq!(part, Some(Timedelta::from_nanos(-3)), "truncated toward zero");
485 }
486
487 #[test]
488 fn nanosecond_accessors_round_down_before_the_origin() {
489 assert_eq!(Timestamp::from_nanos(-1).as_secs(), -1, "the second it falls in");
490 assert_eq!(Uptime::from_secs(90).as_millis(), 90_000, "a coarser unit, counted finer");
491 assert_eq!(
492 Timestamp::from_secs(i64::MAX),
493 Timestamp::MAX,
494 "a count past the range saturates"
495 );
496 }
497
498 proptest! {
499 #[test]
500 fn every_operator_saturates_rather_than_panics(left: i64, right: i64, factor: i64) {
501 let (point, span) = (Timestamp::from_nanos(left), Timedelta::from_nanos(right));
502 black_box((point + span, point - span, point - Timestamp::from_nanos(right)));
503 black_box((span + span, span - span, -span, span * factor, span.abs()));
504 black_box((point.floor(span), point.ceil(span), span.floor(span), span.ceil(span)));
505 }
506
507 #[test]
508 fn a_floor_is_the_multiple_at_or_below(value: i64, unit in 1_i64..1_000_000_000_000) {
509 let multiple = Timestamp::from_nanos(value).floor(Timedelta::from_nanos(unit));
510 prop_assume!(multiple != Timestamp::MIN, "saturated");
511 let distance = value.checked_sub(multiple.as_nanos());
512 prop_assert_eq!(multiple.as_nanos().rem_euclid(unit), 0, "a multiple of the unit");
513 prop_assert!(
514 distance.is_some_and(|distance| (0..unit).contains(&distance)),
515 "less than a unit below"
516 );
517 }
518 }
519}