Skip to main content

t2t_core/
ops.rs

1//! The arithmetic every point and span shares, written once.
2
3use 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/// The nearest multiple of `|unit|` at or below `value`, or `value` for a zero unit.
13#[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/// The nearest multiple of `|unit|` at or above `value`, or `value` for a zero unit.
22#[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
32/// `$count`, an `Option<i64>`, with its count wrapped in `$wrap`: the `Option::map` a
33/// `const fn` cannot call yet.
34macro_rules! wrapped {
35    ($count:expr, $wrap:path) => {
36        match $count {
37            Some(count) => Some($wrap(count)),
38            None => None,
39        }
40    };
41}
42
43/// A span's layout, constants, sign, checked arithmetic and saturating operators.
44macro_rules! span {
45    ($span:ident) => {
46        const _: () = assert!(size_of::<$span>() == size_of::<i64>(), "an `i64`, and nothing else");
47
48        impl $span {
49            /// No time at all.
50            pub const ZERO: Self = Self(0);
51            /// The longest span backwards.
52            pub const MIN: Self = Self(i64::MIN);
53            /// The longest span.
54            pub const MAX: Self = Self(i64::MAX);
55
56            /// Whether the span is zero.
57            #[inline]
58            #[must_use]
59            pub const fn is_zero(self) -> bool {
60                self.0 == 0
61            }
62
63            /// Whether the span runs backwards.
64            #[inline]
65            #[must_use]
66            pub const fn is_negative(self) -> bool {
67                self.0 < 0
68            }
69
70            /// Whether the span runs forwards.
71            #[inline]
72            #[must_use]
73            pub const fn is_positive(self) -> bool {
74                self.0 > 0
75            }
76
77            /// The span forwards, [`MAX`](Self::MAX) for [`MIN`](Self::MIN).
78            #[inline]
79            #[must_use]
80            pub const fn abs(self) -> Self {
81                Self(self.0.saturating_abs())
82            }
83
84            /// The sum, or `None` past the range.
85            #[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            /// The difference, or `None` past the range.
92            #[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            /// The span `factor` times over, or `None` past the range.
99            #[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            /// One of `parts` equal parts, truncated toward zero, or `None` for no parts, or for
106            /// [`MIN`](Self::MIN) in `-1`.
107            #[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            /// The span backwards, or `None` for [`MIN`](Self::MIN).
114            #[inline]
115            #[must_use]
116            pub const fn checked_neg(self) -> Option<Self> {
117                wrapped!(self.0.checked_neg(), Self)
118            }
119
120            /// The span forwards, or `None` for [`MIN`](Self::MIN).
121            #[inline]
122            #[must_use]
123            pub const fn checked_abs(self) -> Option<Self> {
124                wrapped!(self.0.checked_abs(), Self)
125            }
126
127            /// The nearest multiple of `|unit|` at or below the span, or the span for a zero unit.
128            #[inline]
129            #[must_use]
130            pub const fn floor(self, unit: Self) -> Self {
131                Self(floor(self.0, unit.0))
132            }
133
134            /// The nearest multiple of `|unit|` at or above the span, or the span for a zero unit.
135            #[inline]
136            #[must_use]
137            pub const fn ceil(self, unit: Self) -> Self {
138                Self(ceil(self.0, unit.0))
139            }
140        }
141
142        /// Saturates at [`MIN`](Self::MIN) and [`MAX`](Self::MAX).
143        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        /// Saturates at [`MIN`](Self::MIN) and [`MAX`](Self::MAX).
153        impl AddAssign for $span {
154            #[inline]
155            fn add_assign(&mut self, other: Self) {
156                *self = *self + other;
157            }
158        }
159
160        /// Saturates at [`MIN`](Self::MIN) and [`MAX`](Self::MAX).
161        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        /// Saturates at [`MIN`](Self::MIN) and [`MAX`](Self::MAX).
171        impl SubAssign for $span {
172            #[inline]
173            fn sub_assign(&mut self, other: Self) {
174                *self = *self - other;
175            }
176        }
177
178        /// [`MAX`](Self::MAX) for [`MIN`](Self::MIN).
179        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        /// Saturates at [`MIN`](Self::MIN) and [`MAX`](Self::MAX).
189        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        /// Saturates at the span's range.
199        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        /// Saturates at [`MIN`](Self::MIN) and [`MAX`](Self::MAX).
209        impl MulAssign<i64> for $span {
210            #[inline]
211            fn mul_assign(&mut self, factor: i64) {
212                *self = *self * factor;
213            }
214        }
215
216        /// Saturates at [`MIN`](Self::MIN) and [`MAX`](Self::MAX).
217        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        /// Saturates at [`MIN`](Self::MIN) and [`MAX`](Self::MAX).
225        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
234/// A point's layout, constants, checked arithmetic and saturating operators over its span.
235macro_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            /// The earliest point the count holds.
242            pub const MIN: Self = Self(i64::MIN);
243            /// The latest point the count holds.
244            pub const MAX: Self = Self(i64::MAX);
245
246            /// The point `span` later, or `None` past the range.
247            #[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            /// The point `span` earlier, or `None` past the range.
254            #[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            /// The span since `start`, or `None` past the span's range.
261            #[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            /// The nearest multiple of `|unit|` at or below the point, or the point for a zero
268            /// unit.
269            #[inline]
270            #[must_use]
271            pub const fn floor(self, unit: $span) -> Self {
272                Self(floor(self.0, unit.0))
273            }
274
275            /// The nearest multiple of `|unit|` at or above the point, or the point for a zero
276            /// unit.
277            #[inline]
278            #[must_use]
279            pub const fn ceil(self, unit: $span) -> Self {
280                Self(ceil(self.0, unit.0))
281            }
282        }
283
284        /// Saturates at [`MIN`](Self::MIN) and [`MAX`](Self::MAX).
285        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        /// Saturates at [`MIN`](Self::MIN) and [`MAX`](Self::MAX).
295        impl AddAssign<$span> for $point {
296            #[inline]
297            fn add_assign(&mut self, span: $span) {
298                *self = *self + span;
299            }
300        }
301
302        /// Saturates at [`MIN`](Self::MIN) and [`MAX`](Self::MAX).
303        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        /// Saturates at [`MIN`](Self::MIN) and [`MAX`](Self::MAX).
313        impl SubAssign<$span> for $point {
314            #[inline]
315            fn sub_assign(&mut self, span: $span) {
316                *self = *self - span;
317            }
318        }
319
320        /// The span since the other point, saturating at the span's range.
321        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
346/// A nanosecond point's constructors and accessors in each unit, counted from `$origin`.
347macro_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}