pyth_lazer_protocol/
time.rs1#[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#[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 u128::from(self.0) * 1000
46 }
47
48 #[inline]
49 pub fn as_nanos_i128(self) -> i128 {
50 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 #[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 #[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(×tamp)
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 ProtobufTimestamp {
214 #[allow(clippy::cast_possible_wrap)]
215 seconds: (timestamp.0 / 1_000_000) as i64,
216 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#[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 u128::from(self.0) * 1000
302 }
303
304 #[inline]
305 pub fn as_nanos_i128(self) -> i128 {
306 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 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 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 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 #[allow(clippy::cast_possible_wrap)]
459 seconds: (duration.0 / 1_000_000) as i64,
460 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}