Skip to main content

o3/base/
time.rs

1//! Serializable civil-time values without ambient clock authority.
2
3use std::{borrow, marker, num::NonZero, time};
4
5/// Exact duration arithmetic retaining normalized seconds and nanoseconds.
6pub trait DurationExt: Sized {
7    /// Computes floor(((N - 1) * self + sample) / N), for N = 2, 4 or 8.
8    fn average<const N: u64>(self, sample: Self) -> Self;
9}
10
11impl DurationExt for time::Duration {
12    fn average<const N: u64>(self, sample: Self) -> Self {
13        let count = const {
14            assert!(N == 2 || N == 4 || N == 8);
15            NonZero::<u64>::MIN.saturating_add(N - 1)
16        };
17        let weight = const { N - 1 };
18        let secs = weight * (self.as_secs() / count) + sample.as_secs() / count;
19        let nanos = (weight * (self.as_secs() % count) + sample.as_secs() % count)
20            * const { 1_000_000_000 / N }
21            + (weight * u64::from(self.subsec_nanos()) + u64::from(sample.subsec_nanos())) / count;
22        Self::new(secs + nanos / 1_000_000_000, (nanos % 1_000_000_000) as u32)
23    }
24}
25
26/// A representation-preserving proof that a duration is non-zero.
27/// APIs can retain it instead of rechecking the duration.
28#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
29#[repr(transparent)]
30pub struct Positive(time::Duration);
31
32#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub struct Zero;
34
35impl Positive {
36    pub const fn from_secs<const N: u64>() -> Self {
37        const { assert!(N != 0) };
38        Self(time::Duration::from_secs(N))
39    }
40
41    pub const fn from_millis<const N: u64>() -> Self {
42        const { assert!(N != 0) };
43        Self(time::Duration::from_millis(N))
44    }
45
46    pub const fn from_nanos<const N: u64>() -> Self {
47        const { assert!(N != 0) };
48        Self(time::Duration::from_nanos(N))
49    }
50
51    pub const fn try_new(duration: time::Duration) -> Result<Self, Zero> {
52        if duration.is_zero() {
53            Err(Zero)
54        } else {
55            Ok(Self(duration))
56        }
57    }
58
59    pub const fn get(self) -> time::Duration {
60        self.0
61    }
62}
63
64impl borrow::Borrow<time::Duration> for Positive {
65    fn borrow(&self) -> &time::Duration {
66        &self.0
67    }
68}
69
70#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
71pub struct Nanos;
72
73#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
74pub struct Millis;
75
76#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
77pub struct Seconds;
78
79/// Unit-branded ticks elapsed since the Unix epoch.
80#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
81#[repr(transparent)]
82pub struct Unix<U>(u64, marker::PhantomData<fn(U) -> U>);
83
84#[derive(Clone, Copy, Debug, PartialEq, Eq)]
85pub enum Add {
86    Scale {
87        at: Unix<Nanos>,
88        duration: time::Duration,
89    },
90    Range {
91        at: Unix<Nanos>,
92        duration: time::Duration,
93    },
94}
95
96#[derive(Clone, Copy, Debug, PartialEq, Eq)]
97pub enum Sub {
98    Scale {
99        at: Unix<Nanos>,
100        duration: time::Duration,
101    },
102    Range {
103        at: Unix<Nanos>,
104        duration: time::Duration,
105    },
106}
107
108impl<U> Unix<U> {
109    pub const ZERO: Self = Self::new(0);
110
111    pub const fn new(ticks: u64) -> Self {
112        Self(ticks, marker::PhantomData)
113    }
114
115    pub const fn ticks(&self) -> u64 {
116        self.0
117    }
118}
119
120impl Unix<Nanos> {
121    pub fn try_add(self, duration: time::Duration) -> Result<Self, Add> {
122        let Some(ticks) = Self::nanos(duration) else {
123            return Err(Add::Scale { at: self, duration });
124        };
125        if self.0 > u64::MAX - ticks {
126            return Err(Add::Range { at: self, duration });
127        }
128        Ok(Self::new(self.0 + ticks))
129    }
130
131    pub fn try_sub(self, duration: time::Duration) -> Result<Self, Sub> {
132        let Some(ticks) = Self::nanos(duration) else {
133            return Err(Sub::Scale { at: self, duration });
134        };
135        if ticks > self.0 {
136            return Err(Sub::Range { at: self, duration });
137        }
138        Ok(Self::new(self.0 - ticks))
139    }
140
141    fn nanos(duration: time::Duration) -> Option<u64> {
142        let nanos = duration.as_nanos();
143        (nanos <= u64::MAX as u128).then_some(nanos as u64)
144    }
145
146    pub const fn floor_millis(self) -> Unix<Millis> {
147        Unix::new(self.0 / 1_000_000)
148    }
149
150    pub const fn floor_secs(self) -> Unix<Seconds> {
151        Unix::new(self.0 / 1_000_000_000)
152    }
153}
154
155impl Unix<Millis> {
156    pub const fn floor_secs(self) -> Unix<Seconds> {
157        Unix::new(self.0 / 1_000)
158    }
159}
160
161const _: () = {
162    assert!(std::mem::size_of::<Positive>() == std::mem::size_of::<time::Duration>());
163    assert!(std::mem::align_of::<Positive>() == std::mem::align_of::<time::Duration>());
164    assert!(!std::mem::needs_drop::<Positive>());
165};
166const _: () = {
167    assert!(std::mem::size_of::<Unix<Nanos>>() == std::mem::size_of::<u64>());
168    assert!(std::mem::align_of::<Unix<Nanos>>() == std::mem::align_of::<u64>());
169    assert!(!std::mem::needs_drop::<Unix<Nanos>>());
170};
171const _: () = {
172    assert!(
173        std::mem::size_of::<Add>() <= std::mem::size_of::<(Unix<Nanos>, time::Duration, u64)>()
174    );
175    assert!(
176        std::mem::align_of::<Add>() <= std::mem::align_of::<(Unix<Nanos>, time::Duration, u64)>()
177    );
178    assert!(!std::mem::needs_drop::<Add>());
179    assert!(std::mem::size_of::<Sub>() == std::mem::size_of::<Add>());
180    assert!(std::mem::align_of::<Sub>() == std::mem::align_of::<Add>());
181    assert!(!std::mem::needs_drop::<Sub>());
182};