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