pyth_lazer_protocol/
time.rs

1#[cfg(test)]
2mod tests;
3
4use {
5    anyhow::Context,
6    protobuf::{
7        well_known_types::{
8            duration::Duration as ProtobufDuration, timestamp::Timestamp as ProtobufTimestamp,
9        },
10        MessageField,
11    },
12    serde::{Deserialize, Serialize},
13    std::time::{Duration, SystemTime},
14};
15
16/// Unix timestamp with microsecond resolution.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
18#[repr(transparent)]
19pub struct TimestampUs(u64);
20
21#[cfg_attr(feature = "mry", mry::mry)]
22impl TimestampUs {
23    pub fn now() -> Self {
24        SystemTime::now().try_into().expect("invalid system time")
25    }
26}
27
28impl TimestampUs {
29    pub const UNIX_EPOCH: Self = Self(0);
30    pub const MAX: Self = Self(u64::MAX);
31
32    #[inline]
33    pub const fn from_micros(micros: u64) -> Self {
34        Self(micros)
35    }
36
37    #[inline]
38    pub const fn as_micros(self) -> u64 {
39        self.0
40    }
41
42    #[inline]
43    pub fn as_nanos(self) -> u128 {
44        // never overflows
45        u128::from(self.0) * 1000
46    }
47
48    #[inline]
49    pub fn as_nanos_i128(self) -> i128 {
50        // never overflows
51        i128::from(self.0) * 1000
52    }
53
54    #[inline]
55    pub fn from_nanos(nanos: u128) -> anyhow::Result<Self> {
56        let micros = nanos
57            .checked_div(1000)
58            .context("nanos.checked_div(1000) failed")?;
59        Ok(Self::from_micros(micros.try_into()?))
60    }
61
62    #[inline]
63    pub fn from_nanos_i128(nanos: i128) -> anyhow::Result<Self> {
64        let micros = nanos
65            .checked_div(1000)
66            .context("nanos.checked_div(1000) failed")?;
67        Ok(Self::from_micros(micros.try_into()?))
68    }
69
70    #[inline]
71    pub fn as_millis(self) -> u64 {
72        self.0 / 1000
73    }
74
75    #[inline]
76    pub fn from_millis(millis: u64) -> anyhow::Result<Self> {
77        let micros = millis
78            .checked_mul(1000)
79            .context("millis.checked_mul(1000) failed")?;
80        Ok(Self::from_micros(micros))
81    }
82
83    #[inline]
84    pub fn as_secs(self) -> u64 {
85        self.0 / 1_000_000
86    }
87
88    #[inline]
89    pub fn from_secs(secs: u64) -> anyhow::Result<Self> {
90        let micros = secs
91            .checked_mul(1_000_000)
92            .context("secs.checked_mul(1_000_000) failed")?;
93        Ok(Self::from_micros(micros))
94    }
95
96    #[inline]
97    pub fn duration_since(self, other: Self) -> anyhow::Result<DurationUs> {
98        Ok(DurationUs(
99            self.0
100                .checked_sub(other.0)
101                .context("timestamp.checked_sub(duration) failed")?,
102        ))
103    }
104
105    #[inline]
106    pub fn saturating_duration_since(self, other: Self) -> DurationUs {
107        DurationUs(self.0.saturating_sub(other.0))
108    }
109
110    #[inline]
111    pub fn elapsed(self) -> anyhow::Result<DurationUs> {
112        Self::now().duration_since(self)
113    }
114
115    #[inline]
116    pub fn saturating_elapsed(self) -> DurationUs {
117        Self::now().saturating_duration_since(self)
118    }
119
120    #[inline]
121    pub fn saturating_add(self, duration: DurationUs) -> TimestampUs {
122        TimestampUs(self.0.saturating_add(duration.0))
123    }
124
125    #[inline]
126    pub fn saturating_sub(self, duration: DurationUs) -> TimestampUs {
127        TimestampUs(self.0.saturating_sub(duration.0))
128    }
129
130    #[inline]
131    pub fn is_multiple_of(self, duration: DurationUs) -> bool {
132        match self.0.checked_rem(duration.0) {
133            Some(rem) => rem == 0,
134            None => true,
135        }
136    }
137
138    /// Calculates the smallest value greater than or equal to self that is a multiple of `duration`.
139    #[inline]
140    pub fn next_multiple_of(self, duration: DurationUs) -> anyhow::Result<TimestampUs> {
141        Ok(TimestampUs(
142            self.0
143                .checked_next_multiple_of(duration.0)
144                .context("checked_next_multiple_of failed")?,
145        ))
146    }
147
148    /// Calculates the smallest value less than or equal to self that is a multiple of `duration`.
149    #[inline]
150    pub fn previous_multiple_of(self, duration: DurationUs) -> anyhow::Result<TimestampUs> {
151        Ok(TimestampUs(
152            self.0
153                .checked_div(duration.0)
154                .context("checked_div failed")?
155                .checked_mul(duration.0)
156                .context("checked_mul failed")?,
157        ))
158    }
159
160    #[inline]
161    pub fn checked_add(self, duration: DurationUs) -> anyhow::Result<Self> {
162        Ok(TimestampUs(
163            self.0
164                .checked_add(duration.0)
165                .context("checked_add failed")?,
166        ))
167    }
168
169    #[inline]
170    pub fn checked_sub(self, duration: DurationUs) -> anyhow::Result<Self> {
171        Ok(TimestampUs(
172            self.0
173                .checked_sub(duration.0)
174                .context("checked_sub failed")?,
175        ))
176    }
177}
178
179impl TryFrom<ProtobufTimestamp> for TimestampUs {
180    type Error = anyhow::Error;
181
182    #[inline]
183    fn try_from(timestamp: ProtobufTimestamp) -> anyhow::Result<Self> {
184        TryFrom::<&ProtobufTimestamp>::try_from(&timestamp)
185    }
186}
187
188impl TryFrom<&ProtobufTimestamp> for TimestampUs {
189    type Error = anyhow::Error;
190
191    fn try_from(timestamp: &ProtobufTimestamp) -> anyhow::Result<Self> {
192        let seconds_in_micros: u64 = timestamp
193            .seconds
194            .checked_mul(1_000_000)
195            .context("checked_mul failed")?
196            .try_into()?;
197        let nanos_in_micros: u64 = timestamp
198            .nanos
199            .checked_div(1_000)
200            .context("checked_div failed")?
201            .try_into()?;
202        Ok(TimestampUs(
203            seconds_in_micros
204                .checked_add(nanos_in_micros)
205                .context("checked_add failed")?,
206        ))
207    }
208}
209
210impl From<TimestampUs> for ProtobufTimestamp {
211    fn from(timestamp: TimestampUs) -> Self {
212        // u64 to i64 after this division can never overflow because the value cannot be too big
213        ProtobufTimestamp {
214            #[allow(clippy::cast_possible_wrap)]
215            seconds: (timestamp.0 / 1_000_000) as i64,
216            // never fails, never overflows
217            nanos: (timestamp.0 % 1_000_000) as i32 * 1000,
218            special_fields: Default::default(),
219        }
220    }
221}
222
223impl From<TimestampUs> for MessageField<ProtobufTimestamp> {
224    #[inline]
225    fn from(value: TimestampUs) -> Self {
226        MessageField::some(value.into())
227    }
228}
229
230impl TryFrom<SystemTime> for TimestampUs {
231    type Error = anyhow::Error;
232
233    fn try_from(value: SystemTime) -> Result<Self, Self::Error> {
234        let value = value
235            .duration_since(SystemTime::UNIX_EPOCH)
236            .context("invalid system time")?
237            .as_micros()
238            .try_into()?;
239        Ok(Self(value))
240    }
241}
242
243impl TryFrom<TimestampUs> for SystemTime {
244    type Error = anyhow::Error;
245
246    fn try_from(value: TimestampUs) -> Result<Self, Self::Error> {
247        SystemTime::UNIX_EPOCH
248            .checked_add(Duration::from_micros(value.as_micros()))
249            .context("checked_add failed")
250    }
251}
252
253impl TryFrom<&chrono::DateTime<chrono::Utc>> for TimestampUs {
254    type Error = anyhow::Error;
255
256    #[inline]
257    fn try_from(value: &chrono::DateTime<chrono::Utc>) -> Result<Self, Self::Error> {
258        Ok(Self(value.timestamp_micros().try_into()?))
259    }
260}
261
262impl TryFrom<chrono::DateTime<chrono::Utc>> for TimestampUs {
263    type Error = anyhow::Error;
264
265    #[inline]
266    fn try_from(value: chrono::DateTime<chrono::Utc>) -> Result<Self, Self::Error> {
267        TryFrom::<&chrono::DateTime<chrono::Utc>>::try_from(&value)
268    }
269}
270
271impl TryFrom<TimestampUs> for chrono::DateTime<chrono::Utc> {
272    type Error = anyhow::Error;
273
274    #[inline]
275    fn try_from(value: TimestampUs) -> Result<Self, Self::Error> {
276        chrono::DateTime::<chrono::Utc>::from_timestamp_micros(value.as_micros().try_into()?)
277            .with_context(|| format!("cannot convert timestamp to datetime: {value:?}"))
278    }
279}
280
281/// Non-negative duration with microsecond resolution.
282#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
283pub struct DurationUs(u64);
284
285impl DurationUs {
286    pub const ZERO: Self = Self(0);
287
288    #[inline]
289    pub const fn from_micros(micros: u64) -> Self {
290        Self(micros)
291    }
292
293    #[inline]
294    pub const fn as_micros(self) -> u64 {
295        self.0
296    }
297
298    #[inline]
299    pub fn as_nanos(self) -> u128 {
300        // never overflows
301        u128::from(self.0) * 1000
302    }
303
304    #[inline]
305    pub fn as_nanos_i128(self) -> i128 {
306        // never overflows
307        i128::from(self.0) * 1000
308    }
309
310    #[inline]
311    pub fn from_nanos(nanos: u128) -> anyhow::Result<Self> {
312        let micros = nanos.checked_div(1000).context("checked_div failed")?;
313        Ok(Self::from_micros(micros.try_into()?))
314    }
315
316    #[inline]
317    pub fn as_millis(self) -> u64 {
318        self.0 / 1000
319    }
320
321    #[inline]
322    pub const fn from_millis_u32(millis: u32) -> Self {
323        // never overflows
324        Self((millis as u64) * 1_000)
325    }
326
327    #[inline]
328    pub fn from_millis(millis: u64) -> anyhow::Result<Self> {
329        let micros = millis
330            .checked_mul(1000)
331            .context("millis.checked_mul(1000) failed")?;
332        Ok(Self::from_micros(micros))
333    }
334
335    #[inline]
336    pub fn as_secs(self) -> u64 {
337        self.0 / 1_000_000
338    }
339
340    #[inline]
341    pub const fn from_secs_u32(secs: u32) -> Self {
342        // never overflows
343        Self((secs as u64) * 1_000_000)
344    }
345
346    #[inline]
347    pub fn from_secs(secs: u64) -> anyhow::Result<Self> {
348        let micros = secs
349            .checked_mul(1_000_000)
350            .context("secs.checked_mul(1_000_000) failed")?;
351        Ok(Self::from_micros(micros))
352    }
353
354    #[inline]
355    pub fn from_days_u16(days: u16) -> Self {
356        // never overflows
357        Self((days as u64) * 24 * 3600 * 1_000_000)
358    }
359
360    #[inline]
361    pub fn is_multiple_of(self, other: DurationUs) -> bool {
362        match self.0.checked_rem(other.0) {
363            Some(rem) => rem == 0,
364            None => true,
365        }
366    }
367
368    #[inline]
369    pub const fn is_zero(self) -> bool {
370        self.0 == 0
371    }
372
373    #[inline]
374    pub const fn is_positive(self) -> bool {
375        self.0 > 0
376    }
377
378    #[inline]
379    pub fn checked_add(self, other: DurationUs) -> anyhow::Result<Self> {
380        Ok(DurationUs(
381            self.0.checked_add(other.0).context("checked_add failed")?,
382        ))
383    }
384
385    #[inline]
386    pub fn checked_sub(self, other: DurationUs) -> anyhow::Result<Self> {
387        Ok(DurationUs(
388            self.0.checked_sub(other.0).context("checked_sub failed")?,
389        ))
390    }
391
392    #[inline]
393    pub fn checked_mul(self, n: u64) -> anyhow::Result<DurationUs> {
394        Ok(DurationUs(
395            self.0.checked_mul(n).context("checked_mul failed")?,
396        ))
397    }
398
399    #[inline]
400    pub fn checked_div(self, n: u64) -> anyhow::Result<DurationUs> {
401        Ok(DurationUs(
402            self.0.checked_div(n).context("checked_div failed")?,
403        ))
404    }
405}
406
407impl From<DurationUs> for Duration {
408    #[inline]
409    fn from(value: DurationUs) -> Self {
410        Duration::from_micros(value.as_micros())
411    }
412}
413
414impl TryFrom<Duration> for DurationUs {
415    type Error = anyhow::Error;
416
417    #[inline]
418    fn try_from(value: Duration) -> Result<Self, Self::Error> {
419        Ok(Self(value.as_micros().try_into()?))
420    }
421}
422
423impl TryFrom<ProtobufDuration> for DurationUs {
424    type Error = anyhow::Error;
425
426    #[inline]
427    fn try_from(duration: ProtobufDuration) -> anyhow::Result<Self> {
428        TryFrom::<&ProtobufDuration>::try_from(&duration)
429    }
430}
431
432impl TryFrom<&ProtobufDuration> for DurationUs {
433    type Error = anyhow::Error;
434
435    fn try_from(duration: &ProtobufDuration) -> anyhow::Result<Self> {
436        let seconds_in_micros: u64 = duration
437            .seconds
438            .checked_mul(1_000_000)
439            .context("checked_mul failed")?
440            .try_into()?;
441        let nanos_in_micros: u64 = duration
442            .nanos
443            .checked_div(1_000)
444            .context("nanos.checked_div(1_000) failed")?
445            .try_into()?;
446        Ok(DurationUs(
447            seconds_in_micros
448                .checked_add(nanos_in_micros)
449                .context("checked_add failed")?,
450        ))
451    }
452}
453
454impl From<DurationUs> for ProtobufDuration {
455    fn from(duration: DurationUs) -> Self {
456        ProtobufDuration {
457            // u64 to i64 after this division can never overflow because the value cannot be too big
458            #[allow(clippy::cast_possible_wrap)]
459            seconds: (duration.0 / 1_000_000) as i64,
460            // never fails, never overflows
461            nanos: (duration.0 % 1_000_000) as i32 * 1000,
462            special_fields: Default::default(),
463        }
464    }
465}
466
467pub mod duration_us_serde_humantime {
468    use std::time::Duration;
469
470    use serde::{de::Error, Deserialize, Serialize};
471
472    use crate::time::DurationUs;
473
474    pub fn serialize<S>(value: &DurationUs, serializer: S) -> Result<S::Ok, S::Error>
475    where
476        S: serde::Serializer,
477    {
478        humantime_serde::Serde::from(Duration::from(*value)).serialize(serializer)
479    }
480
481    pub fn deserialize<'de, D>(deserializer: D) -> Result<DurationUs, D::Error>
482    where
483        D: serde::Deserializer<'de>,
484    {
485        let value = humantime_serde::Serde::<Duration>::deserialize(deserializer)?;
486        value.into_inner().try_into().map_err(D::Error::custom)
487    }
488}