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 utoipa::ToSchema,
15};
16
17#[derive(
19 Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, ToSchema,
20)]
21#[repr(transparent)]
22#[schema(value_type = u64)]
23pub struct TimestampUs(u64);
24
25#[cfg_attr(feature = "mry", mry::mry)]
26impl TimestampUs {
27 pub fn now() -> Self {
28 SystemTime::now().try_into().expect("invalid system time")
29 }
30}
31
32impl TimestampUs {
33 pub const UNIX_EPOCH: Self = Self(0);
34 pub const MAX: Self = Self(u64::MAX);
35
36 #[inline]
37 pub const fn from_micros(micros: u64) -> Self {
38 Self(micros)
39 }
40
41 #[inline]
42 pub const fn as_micros(self) -> u64 {
43 self.0
44 }
45
46 #[inline]
47 pub fn as_nanos(self) -> u128 {
48 u128::from(self.0) * 1000
50 }
51
52 #[inline]
53 pub fn as_nanos_i128(self) -> i128 {
54 i128::from(self.0) * 1000
56 }
57
58 #[inline]
59 pub fn from_nanos(nanos: u128) -> anyhow::Result<Self> {
60 let micros = nanos
61 .checked_div(1000)
62 .context("nanos.checked_div(1000) failed")?;
63 Ok(Self::from_micros(micros.try_into()?))
64 }
65
66 #[inline]
67 pub fn from_nanos_i128(nanos: i128) -> anyhow::Result<Self> {
68 let micros = nanos
69 .checked_div(1000)
70 .context("nanos.checked_div(1000) failed")?;
71 Ok(Self::from_micros(micros.try_into()?))
72 }
73
74 #[inline]
75 pub fn as_millis(self) -> u64 {
76 self.0 / 1000
77 }
78
79 #[inline]
80 pub fn from_millis(millis: u64) -> anyhow::Result<Self> {
81 let micros = millis
82 .checked_mul(1000)
83 .context("millis.checked_mul(1000) failed")?;
84 Ok(Self::from_micros(micros))
85 }
86
87 #[inline]
88 pub fn as_secs(self) -> u64 {
89 self.0 / 1_000_000
90 }
91
92 #[inline]
93 pub fn from_secs(secs: u64) -> anyhow::Result<Self> {
94 let micros = secs
95 .checked_mul(1_000_000)
96 .context("secs.checked_mul(1_000_000) failed")?;
97 Ok(Self::from_micros(micros))
98 }
99
100 #[inline]
101 pub fn duration_since(self, other: Self) -> anyhow::Result<DurationUs> {
102 Ok(DurationUs(
103 self.0
104 .checked_sub(other.0)
105 .context("timestamp.checked_sub(duration) failed")?,
106 ))
107 }
108
109 #[inline]
110 pub fn saturating_duration_since(self, other: Self) -> DurationUs {
111 DurationUs(self.0.saturating_sub(other.0))
112 }
113
114 #[inline]
115 pub fn elapsed(self) -> anyhow::Result<DurationUs> {
116 Self::now().duration_since(self)
117 }
118
119 #[inline]
120 pub fn saturating_elapsed(self) -> DurationUs {
121 Self::now().saturating_duration_since(self)
122 }
123
124 #[inline]
125 pub fn saturating_add(self, duration: DurationUs) -> TimestampUs {
126 TimestampUs(self.0.saturating_add(duration.0))
127 }
128
129 #[inline]
130 pub fn saturating_sub(self, duration: DurationUs) -> TimestampUs {
131 TimestampUs(self.0.saturating_sub(duration.0))
132 }
133
134 #[inline]
135 pub fn is_multiple_of(self, duration: DurationUs) -> bool {
136 match self.0.checked_rem(duration.0) {
137 Some(rem) => rem == 0,
138 None => true,
139 }
140 }
141
142 #[inline]
144 pub fn next_multiple_of(self, duration: DurationUs) -> anyhow::Result<TimestampUs> {
145 Ok(TimestampUs(
146 self.0
147 .checked_next_multiple_of(duration.0)
148 .context("checked_next_multiple_of failed")?,
149 ))
150 }
151
152 #[inline]
154 pub fn previous_multiple_of(self, duration: DurationUs) -> anyhow::Result<TimestampUs> {
155 Ok(TimestampUs(
156 self.0
157 .checked_div(duration.0)
158 .context("checked_div failed")?
159 .checked_mul(duration.0)
160 .context("checked_mul failed")?,
161 ))
162 }
163
164 #[inline]
165 pub fn checked_add(self, duration: DurationUs) -> anyhow::Result<Self> {
166 Ok(TimestampUs(
167 self.0
168 .checked_add(duration.0)
169 .context("checked_add failed")?,
170 ))
171 }
172
173 #[inline]
174 pub fn checked_sub(self, duration: DurationUs) -> anyhow::Result<Self> {
175 Ok(TimestampUs(
176 self.0
177 .checked_sub(duration.0)
178 .context("checked_sub failed")?,
179 ))
180 }
181}
182
183impl TryFrom<ProtobufTimestamp> for TimestampUs {
184 type Error = anyhow::Error;
185
186 #[inline]
187 fn try_from(timestamp: ProtobufTimestamp) -> anyhow::Result<Self> {
188 TryFrom::<&ProtobufTimestamp>::try_from(×tamp)
189 }
190}
191
192impl TryFrom<&ProtobufTimestamp> for TimestampUs {
193 type Error = anyhow::Error;
194
195 fn try_from(timestamp: &ProtobufTimestamp) -> anyhow::Result<Self> {
196 let seconds_in_micros: u64 = timestamp
197 .seconds
198 .checked_mul(1_000_000)
199 .context("checked_mul failed")?
200 .try_into()?;
201 let nanos_in_micros: u64 = timestamp
202 .nanos
203 .checked_div(1_000)
204 .context("checked_div failed")?
205 .try_into()?;
206 Ok(TimestampUs(
207 seconds_in_micros
208 .checked_add(nanos_in_micros)
209 .context("checked_add failed")?,
210 ))
211 }
212}
213
214impl From<TimestampUs> for ProtobufTimestamp {
215 fn from(timestamp: TimestampUs) -> Self {
216 ProtobufTimestamp {
218 #[allow(clippy::cast_possible_wrap)]
219 seconds: (timestamp.0 / 1_000_000) as i64,
220 nanos: (timestamp.0 % 1_000_000) as i32 * 1000,
222 special_fields: Default::default(),
223 }
224 }
225}
226
227impl From<TimestampUs> for MessageField<ProtobufTimestamp> {
228 #[inline]
229 fn from(value: TimestampUs) -> Self {
230 MessageField::some(value.into())
231 }
232}
233
234impl TryFrom<SystemTime> for TimestampUs {
235 type Error = anyhow::Error;
236
237 fn try_from(value: SystemTime) -> Result<Self, Self::Error> {
238 let value = value
239 .duration_since(SystemTime::UNIX_EPOCH)
240 .context("invalid system time")?
241 .as_micros()
242 .try_into()?;
243 Ok(Self(value))
244 }
245}
246
247impl TryFrom<TimestampUs> for SystemTime {
248 type Error = anyhow::Error;
249
250 fn try_from(value: TimestampUs) -> Result<Self, Self::Error> {
251 SystemTime::UNIX_EPOCH
252 .checked_add(Duration::from_micros(value.as_micros()))
253 .context("checked_add failed")
254 }
255}
256
257impl TryFrom<&chrono::DateTime<chrono::Utc>> for TimestampUs {
258 type Error = anyhow::Error;
259
260 #[inline]
261 fn try_from(value: &chrono::DateTime<chrono::Utc>) -> Result<Self, Self::Error> {
262 Ok(Self(value.timestamp_micros().try_into()?))
263 }
264}
265
266impl TryFrom<chrono::DateTime<chrono::Utc>> for TimestampUs {
267 type Error = anyhow::Error;
268
269 #[inline]
270 fn try_from(value: chrono::DateTime<chrono::Utc>) -> Result<Self, Self::Error> {
271 TryFrom::<&chrono::DateTime<chrono::Utc>>::try_from(&value)
272 }
273}
274
275impl TryFrom<TimestampUs> for chrono::DateTime<chrono::Utc> {
276 type Error = anyhow::Error;
277
278 #[inline]
279 fn try_from(value: TimestampUs) -> Result<Self, Self::Error> {
280 chrono::DateTime::<chrono::Utc>::from_timestamp_micros(value.as_micros().try_into()?)
281 .with_context(|| format!("cannot convert timestamp to datetime: {value:?}"))
282 }
283}
284
285#[derive(
287 Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, ToSchema,
288)]
289#[schema(value_type = u64)]
290pub struct DurationUs(u64);
291
292impl DurationUs {
293 pub const ZERO: Self = Self(0);
294
295 #[inline]
296 pub const fn from_micros(micros: u64) -> Self {
297 Self(micros)
298 }
299
300 #[inline]
301 pub const fn as_micros(self) -> u64 {
302 self.0
303 }
304
305 #[inline]
306 pub fn as_nanos(self) -> u128 {
307 u128::from(self.0) * 1000
309 }
310
311 #[inline]
312 pub fn as_nanos_i128(self) -> i128 {
313 i128::from(self.0) * 1000
315 }
316
317 #[inline]
318 pub fn from_nanos(nanos: u128) -> anyhow::Result<Self> {
319 let micros = nanos.checked_div(1000).context("checked_div failed")?;
320 Ok(Self::from_micros(micros.try_into()?))
321 }
322
323 #[inline]
324 pub fn as_millis(self) -> u64 {
325 self.0 / 1000
326 }
327
328 #[inline]
329 pub const fn from_millis_u32(millis: u32) -> Self {
330 Self((millis as u64) * 1_000)
332 }
333
334 #[inline]
335 pub fn from_millis(millis: u64) -> anyhow::Result<Self> {
336 let micros = millis
337 .checked_mul(1000)
338 .context("millis.checked_mul(1000) failed")?;
339 Ok(Self::from_micros(micros))
340 }
341
342 #[inline]
343 pub fn as_secs(self) -> u64 {
344 self.0 / 1_000_000
345 }
346
347 #[inline]
348 pub const fn from_secs_u32(secs: u32) -> Self {
349 Self((secs as u64) * 1_000_000)
351 }
352
353 #[inline]
354 pub fn from_secs(secs: u64) -> anyhow::Result<Self> {
355 let micros = secs
356 .checked_mul(1_000_000)
357 .context("secs.checked_mul(1_000_000) failed")?;
358 Ok(Self::from_micros(micros))
359 }
360
361 #[inline]
362 pub fn from_days_u16(days: u16) -> Self {
363 Self((days as u64) * 24 * 3600 * 1_000_000)
365 }
366
367 #[inline]
368 pub fn is_multiple_of(self, other: DurationUs) -> bool {
369 match self.0.checked_rem(other.0) {
370 Some(rem) => rem == 0,
371 None => true,
372 }
373 }
374
375 #[inline]
376 pub const fn is_zero(self) -> bool {
377 self.0 == 0
378 }
379
380 #[inline]
381 pub const fn is_positive(self) -> bool {
382 self.0 > 0
383 }
384
385 #[inline]
386 pub fn checked_add(self, other: DurationUs) -> anyhow::Result<Self> {
387 Ok(DurationUs(
388 self.0.checked_add(other.0).context("checked_add failed")?,
389 ))
390 }
391
392 #[inline]
393 pub fn checked_sub(self, other: DurationUs) -> anyhow::Result<Self> {
394 Ok(DurationUs(
395 self.0.checked_sub(other.0).context("checked_sub failed")?,
396 ))
397 }
398
399 #[inline]
400 pub fn checked_mul(self, n: u64) -> anyhow::Result<DurationUs> {
401 Ok(DurationUs(
402 self.0.checked_mul(n).context("checked_mul failed")?,
403 ))
404 }
405
406 #[inline]
407 pub fn checked_div(self, n: u64) -> anyhow::Result<DurationUs> {
408 Ok(DurationUs(
409 self.0.checked_div(n).context("checked_div failed")?,
410 ))
411 }
412}
413
414impl From<DurationUs> for Duration {
415 #[inline]
416 fn from(value: DurationUs) -> Self {
417 Duration::from_micros(value.as_micros())
418 }
419}
420
421impl TryFrom<Duration> for DurationUs {
422 type Error = anyhow::Error;
423
424 #[inline]
425 fn try_from(value: Duration) -> Result<Self, Self::Error> {
426 Ok(Self(value.as_micros().try_into()?))
427 }
428}
429
430impl TryFrom<ProtobufDuration> for DurationUs {
431 type Error = anyhow::Error;
432
433 #[inline]
434 fn try_from(duration: ProtobufDuration) -> anyhow::Result<Self> {
435 TryFrom::<&ProtobufDuration>::try_from(&duration)
436 }
437}
438
439impl TryFrom<&ProtobufDuration> for DurationUs {
440 type Error = anyhow::Error;
441
442 fn try_from(duration: &ProtobufDuration) -> anyhow::Result<Self> {
443 let seconds_in_micros: u64 = duration
444 .seconds
445 .checked_mul(1_000_000)
446 .context("checked_mul failed")?
447 .try_into()?;
448 let nanos_in_micros: u64 = duration
449 .nanos
450 .checked_div(1_000)
451 .context("nanos.checked_div(1_000) failed")?
452 .try_into()?;
453 Ok(DurationUs(
454 seconds_in_micros
455 .checked_add(nanos_in_micros)
456 .context("checked_add failed")?,
457 ))
458 }
459}
460
461impl From<DurationUs> for ProtobufDuration {
462 fn from(duration: DurationUs) -> Self {
463 ProtobufDuration {
464 #[allow(clippy::cast_possible_wrap)]
466 seconds: (duration.0 / 1_000_000) as i64,
467 nanos: (duration.0 % 1_000_000) as i32 * 1000,
469 special_fields: Default::default(),
470 }
471 }
472}
473
474pub mod duration_us_serde_humantime {
475 use std::time::Duration;
476
477 use serde::{de::Error, Deserialize, Serialize};
478
479 use crate::time::DurationUs;
480
481 pub fn serialize<S>(value: &DurationUs, serializer: S) -> Result<S::Ok, S::Error>
482 where
483 S: serde::Serializer,
484 {
485 humantime_serde::Serde::from(Duration::from(*value)).serialize(serializer)
486 }
487
488 pub fn deserialize<'de, D>(deserializer: D) -> Result<DurationUs, D::Error>
489 where
490 D: serde::Deserializer<'de>,
491 {
492 let value = humantime_serde::Serde::<Duration>::deserialize(deserializer)?;
493 value.into_inner().try_into().map_err(D::Error::custom)
494 }
495}
496
497#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, ToSchema)]
498#[schema(as = String, example = "fixed_rate@200ms")]
499pub struct FixedRate {
500 rate: DurationUs,
501}
502
503impl FixedRate {
504 pub const RATE_50_MS: Self = Self {
505 rate: DurationUs::from_millis_u32(50),
506 };
507 pub const RATE_200_MS: Self = Self {
508 rate: DurationUs::from_millis_u32(200),
509 };
510 pub const RATE_1000_MS: Self = Self {
511 rate: DurationUs::from_millis_u32(1000),
512 };
513
514 pub const ALL: [Self; 3] = [Self::RATE_50_MS, Self::RATE_200_MS, Self::RATE_1000_MS];
519 pub const MIN: Self = Self::ALL[0];
520
521 pub fn from_millis(millis: u32) -> Option<Self> {
522 Self::ALL
523 .into_iter()
524 .find(|v| v.rate.as_millis() == u64::from(millis))
525 }
526
527 pub fn duration(self) -> DurationUs {
528 self.rate
529 }
530}
531
532impl TryFrom<DurationUs> for FixedRate {
533 type Error = anyhow::Error;
534
535 fn try_from(value: DurationUs) -> Result<Self, Self::Error> {
536 Self::ALL
537 .into_iter()
538 .find(|v| v.rate == value)
539 .with_context(|| format!("unsupported rate: {value:?}"))
540 }
541}
542
543impl TryFrom<&ProtobufDuration> for FixedRate {
544 type Error = anyhow::Error;
545
546 fn try_from(value: &ProtobufDuration) -> Result<Self, Self::Error> {
547 let duration = DurationUs::try_from(value)?;
548 Self::try_from(duration)
549 }
550}
551
552impl TryFrom<ProtobufDuration> for FixedRate {
553 type Error = anyhow::Error;
554
555 fn try_from(duration: ProtobufDuration) -> anyhow::Result<Self> {
556 TryFrom::<&ProtobufDuration>::try_from(&duration)
557 }
558}
559
560impl From<FixedRate> for DurationUs {
561 fn from(value: FixedRate) -> Self {
562 value.rate
563 }
564}
565
566impl From<FixedRate> for ProtobufDuration {
567 fn from(value: FixedRate) -> Self {
568 value.rate.into()
569 }
570}
571
572#[test]
573fn fixed_rate_values() {
574 assert!(
575 FixedRate::ALL.windows(2).all(|w| w[0] < w[1]),
576 "values must be unique and sorted"
577 );
578 for value in FixedRate::ALL {
579 assert_eq!(
580 1_000_000 % value.duration().as_micros(),
581 0,
582 "1 s must contain whole number of intervals"
583 );
584 assert_eq!(
585 value.duration().as_micros() % FixedRate::MIN.duration().as_micros(),
586 0,
587 "the interval's borders must be a subset of the minimal interval's borders"
588 );
589 }
590}