1use std::num::NonZero;
2
3use crate::coding::VarInt;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
9#[error("time overflow")]
10pub struct TimeOverflow;
11
12#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
21pub struct Timescale(NonZero<u64>);
22
23impl Timescale {
24 pub const SECOND: Self = match Self::new(1) {
26 Ok(scale) => scale,
27 Err(_) => unreachable!(),
28 };
29 pub const MILLI: Self = match Self::new(1_000) {
31 Ok(scale) => scale,
32 Err(_) => unreachable!(),
33 };
34 pub const MICRO: Self = match Self::new(1_000_000) {
37 Ok(scale) => scale,
38 Err(_) => unreachable!(),
39 };
40 pub const NANO: Self = match Self::new(1_000_000_000) {
42 Ok(scale) => scale,
43 Err(_) => unreachable!(),
44 };
45
46 pub const fn new(units_per_second: u64) -> Result<Self, TimeOverflow> {
52 if VarInt::from_u64(units_per_second).is_none() {
55 return Err(TimeOverflow);
56 }
57 match NonZero::new(units_per_second) {
58 Some(n) => Ok(Self(n)),
59 None => Err(TimeOverflow),
60 }
61 }
62
63 pub const fn as_u64(self) -> u64 {
65 self.0.get()
66 }
67}
68
69impl TryFrom<u64> for Timescale {
70 type Error = TimeOverflow;
71
72 fn try_from(units_per_second: u64) -> Result<Self, Self::Error> {
73 Self::new(units_per_second)
74 }
75}
76
77impl TryFrom<NonZero<u64>> for Timescale {
78 type Error = TimeOverflow;
79
80 fn try_from(units_per_second: NonZero<u64>) -> Result<Self, Self::Error> {
83 Self::new(units_per_second.get())
84 }
85}
86
87impl From<Timescale> for u64 {
88 fn from(scale: Timescale) -> Self {
89 scale.0.get()
90 }
91}
92
93impl From<Timescale> for NonZero<u64> {
94 fn from(scale: Timescale) -> Self {
95 scale.0
96 }
97}
98
99impl Default for Timescale {
100 fn default() -> Self {
104 Self::MILLI
105 }
106}
107
108impl std::fmt::Debug for Timescale {
109 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110 match *self {
111 Self::SECOND => write!(f, "Timescale::SECOND"),
112 Self::MILLI => write!(f, "Timescale::MILLI"),
113 Self::MICRO => write!(f, "Timescale::MICRO"),
114 Self::NANO => write!(f, "Timescale::NANO"),
115 Self(n) => write!(f, "Timescale({n})"),
116 }
117 }
118}
119
120impl std::fmt::Display for Timescale {
121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122 write!(f, "{}", self.0)
123 }
124}
125
126#[derive(Clone, Copy, PartialEq, Eq, Hash)]
159pub struct Timestamp {
160 value: VarInt,
161 scale: Timescale,
162}
163
164impl Timestamp {
165 pub const ZERO: Self = Self::new_const(0, Timescale::SECOND);
173
174 pub const fn new(value: u64, scale: Timescale) -> Result<Self, TimeOverflow> {
177 match VarInt::from_u64(value) {
178 Some(value) => Ok(Self { value, scale }),
179 None => Err(TimeOverflow),
180 }
181 }
182
183 const fn new_const(value: u64, scale: Timescale) -> Self {
185 match Self::new(value, scale) {
186 Ok(time) => time,
187 Err(_) => panic!("timestamp value exceeds 2^62 - 1"),
188 }
189 }
190
191 pub fn from_scale(value: u64, units_per_second: u64) -> Result<Self, TimeOverflow> {
194 Self::new(value, Timescale::new(units_per_second)?)
195 }
196
197 pub const fn from_secs(seconds: u64) -> Result<Self, TimeOverflow> {
199 Self::new(seconds, Timescale::SECOND)
200 }
201
202 pub const fn from_millis(millis: u64) -> Result<Self, TimeOverflow> {
204 Self::new(millis, Timescale::MILLI)
205 }
206
207 pub const fn from_micros(micros: u64) -> Result<Self, TimeOverflow> {
209 Self::new(micros, Timescale::MICRO)
210 }
211
212 pub const fn from_nanos(nanos: u64) -> Result<Self, TimeOverflow> {
214 Self::new(nanos, Timescale::NANO)
215 }
216
217 pub const fn value(self) -> u64 {
219 self.value.into_inner()
220 }
221
222 pub const fn scale(self) -> Timescale {
224 self.scale
225 }
226
227 pub const fn is_zero(self) -> bool {
229 self.value.into_inner() == 0
230 }
231
232 pub const fn convert(self, new_scale: Timescale) -> Result<Self, TimeOverflow> {
235 if self.scale.0.get() == new_scale.0.get() {
236 return Ok(self);
237 }
238 match (self.value.into_inner() as u128).checked_mul(new_scale.0.get() as u128) {
239 Some(scaled) => match VarInt::from_u128(scaled / self.scale.0.get() as u128) {
240 Some(value) => Ok(Self {
241 value,
242 scale: new_scale,
243 }),
244 None => Err(TimeOverflow),
245 },
246 None => Err(TimeOverflow),
247 }
248 }
249
250 pub const fn as_scale(self, target: Timescale) -> u128 {
252 self.value.into_inner() as u128 * target.0.get() as u128 / self.scale.0.get() as u128
253 }
254
255 pub const fn as_secs(self) -> u64 {
257 self.value.into_inner() / self.scale.0.get()
258 }
259
260 pub const fn as_millis(self) -> u128 {
262 self.as_scale(Timescale::MILLI)
263 }
264
265 pub const fn as_micros(self) -> u128 {
267 self.as_scale(Timescale::MICRO)
268 }
269
270 pub const fn as_nanos(self) -> u128 {
272 self.as_scale(Timescale::NANO)
273 }
274
275 pub const fn checked_add(self, rhs: Self) -> Result<Self, TimeOverflow> {
278 if self.scale.0.get() != rhs.scale.0.get() {
279 return Err(TimeOverflow);
280 }
281 match self.value.into_inner().checked_add(rhs.value.into_inner()) {
282 Some(result) => Self::new(result, self.scale),
283 None => Err(TimeOverflow),
284 }
285 }
286
287 pub const fn checked_sub(self, rhs: Self) -> Result<Self, TimeOverflow> {
290 if self.scale.0.get() != rhs.scale.0.get() {
291 return Err(TimeOverflow);
292 }
293 match self.value.into_inner().checked_sub(rhs.value.into_inner()) {
294 Some(result) => Self::new(result, self.scale),
295 None => Err(TimeOverflow),
296 }
297 }
298
299 pub fn now() -> Self {
308 clock::now()
309 }
310}
311
312impl TryFrom<std::time::Duration> for Timestamp {
313 type Error = TimeOverflow;
314
315 fn try_from(duration: std::time::Duration) -> Result<Self, Self::Error> {
317 match VarInt::from_u128(duration.as_nanos()) {
318 Some(value) => Ok(Self {
319 value,
320 scale: Timescale::NANO,
321 }),
322 None => Err(TimeOverflow),
323 }
324 }
325}
326
327impl From<Timestamp> for std::time::Duration {
328 fn from(time: Timestamp) -> Self {
329 let nanos = time.as_nanos();
330 std::time::Duration::new(time.as_secs(), (nanos % 1_000_000_000) as u32)
331 }
332}
333
334impl std::fmt::Debug for Timestamp {
335 #[allow(clippy::manual_is_multiple_of)] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
337 let nanos = self.as_nanos();
338
339 if nanos % 1_000_000_000 == 0 {
341 write!(f, "{}s", nanos / 1_000_000_000)
342 } else if nanos % 1_000_000 == 0 {
343 write!(f, "{}ms", nanos / 1_000_000)
344 } else if nanos % 1_000 == 0 {
345 write!(f, "{}µs", nanos / 1_000)
346 } else {
347 write!(f, "{}ns", nanos)
348 }
349 }
350}
351
352impl PartialOrd for Timestamp {
353 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
354 Some(self.cmp(other))
355 }
356}
357
358impl Ord for Timestamp {
359 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
367 if self.scale.0.get() == other.scale.0.get() {
368 return self.value.cmp(&other.value);
369 }
370 let lhs = self.value.into_inner() as u128 * other.scale.0.get() as u128;
371 let rhs = other.value.into_inner() as u128 * self.scale.0.get() as u128;
372 lhs.cmp(&rhs)
373 .then_with(|| self.scale.0.get().cmp(&other.scale.0.get()))
374 .then_with(|| self.value.cmp(&other.value))
375 }
376}
377
378#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
379mod clock {
380 use std::sync::LazyLock;
381 use std::time::{SystemTime, UNIX_EPOCH};
382
383 use rand::RngExt;
384
385 use super::Timestamp;
386
387 const ANCHOR_EPOCH_SECS: u64 = 1_577_836_800;
393
394 static TIME_ANCHOR: LazyLock<(std::time::Instant, SystemTime)> = LazyLock::new(|| {
396 let jitter = std::time::Duration::from_millis(rand::rng().random_range(0..69_420));
400 (std::time::Instant::now(), SystemTime::now() - jitter)
401 });
402
403 pub(super) fn now() -> Timestamp {
404 from_std_instant(crate::model::clock::now())
405 }
406
407 fn from_std_instant(instant: std::time::Instant) -> Timestamp {
408 let (anchor_instant, anchor_system) = *TIME_ANCHOR;
409
410 let system = match instant.checked_duration_since(anchor_instant) {
411 Some(forward) => anchor_system + forward,
412 None => anchor_system - anchor_instant.duration_since(instant),
413 };
414
415 let epoch = UNIX_EPOCH + std::time::Duration::from_secs(ANCHOR_EPOCH_SECS);
416 let duration = system.duration_since(epoch).unwrap_or(std::time::Duration::ZERO);
419
420 Timestamp::from_millis(duration.as_millis() as u64).expect("clock is somehow past the year 2300")
421 }
422
423 impl From<std::time::Instant> for Timestamp {
424 fn from(instant: std::time::Instant) -> Self {
430 from_std_instant(instant)
431 }
432 }
433}
434
435#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
436mod clock {
437 use std::sync::LazyLock;
438
439 use rand::RngExt;
440
441 use super::Timestamp;
442
443 static TIME_ANCHOR: LazyLock<(crate::runtime::Instant, std::time::Duration)> = LazyLock::new(|| {
444 let jitter = std::time::Duration::from_millis(rand::rng().random_range(1..69_420));
445 (crate::model::clock::now(), jitter)
446 });
447
448 pub(super) fn now() -> Timestamp {
449 crate::model::clock::now().into()
450 }
451
452 impl From<crate::time::Instant> for Timestamp {
453 fn from(instant: crate::time::Instant) -> Timestamp {
454 let (anchor_instant, anchor_duration) = *TIME_ANCHOR;
455 let duration = match instant.checked_duration_since(anchor_instant) {
456 Some(forward) => anchor_duration + forward,
457 None => anchor_duration
458 .checked_sub(anchor_instant.duration_since(instant))
459 .unwrap_or(std::time::Duration::ZERO),
460 };
461
462 Timestamp::from_millis(duration.as_millis() as u64).expect("clock is somehow past the year 2300")
463 }
464 }
465}
466
467#[cfg(test)]
468mod tests {
469 use super::*;
470
471 #[test]
472 fn test_from_secs() {
473 let time = Timestamp::from_secs(5).unwrap();
474 assert_eq!(time.scale(), Timescale::SECOND);
475 assert_eq!(time.as_secs(), 5);
476 assert_eq!(time.as_millis(), 5000);
477 assert_eq!(time.as_micros(), 5_000_000);
478 assert_eq!(time.as_nanos(), 5_000_000_000);
479 }
480
481 #[test]
482 fn test_from_millis() {
483 let time = Timestamp::from_millis(5000).unwrap();
484 assert_eq!(time.scale(), Timescale::MILLI);
485 assert_eq!(time.as_secs(), 5);
486 assert_eq!(time.as_millis(), 5000);
487 }
488
489 #[test]
490 fn test_from_micros() {
491 let time = Timestamp::from_micros(5_000_000).unwrap();
492 assert_eq!(time.scale(), Timescale::MICRO);
493 assert_eq!(time.as_secs(), 5);
494 assert_eq!(time.as_micros(), 5_000_000);
495 }
496
497 #[test]
498 fn test_from_nanos() {
499 let time = Timestamp::from_nanos(5_000_000_000).unwrap();
500 assert_eq!(time.scale(), Timescale::NANO);
501 assert_eq!(time.as_secs(), 5);
502 assert_eq!(time.as_nanos(), 5_000_000_000);
503 }
504
505 #[test]
506 fn test_timescale_new_rejects_zero_and_overflow() {
507 assert!(Timescale::new(0).is_err());
508 assert!(Timescale::new(1).is_ok());
509 assert_eq!(Timescale::new(1).unwrap(), Timescale::SECOND);
510 assert_eq!(Timescale::new(1_000).unwrap(), Timescale::MILLI);
511
512 assert!(Timescale::new(1u64 << 62).is_err());
514 assert!(Timescale::new((1u64 << 62) - 1).is_ok());
516 }
517
518 #[test]
519 fn test_timescale_try_from_nonzero_enforces_varint() {
520 use std::num::NonZero;
521
522 assert_eq!(
523 Timescale::try_from(NonZero::new(1).unwrap()).unwrap(),
524 Timescale::SECOND
525 );
526 assert_eq!(
527 Timescale::try_from(NonZero::new((1u64 << 62) - 1).unwrap())
528 .unwrap()
529 .as_u64(),
530 (1u64 << 62) - 1
531 );
532 assert!(Timescale::try_from(NonZero::new(1u64 << 62).unwrap()).is_err());
533 assert!(Timescale::try_from(NonZero::new(u64::MAX).unwrap()).is_err());
534 }
535
536 #[test]
537 fn test_convert_to_finer() {
538 let time_ms = Timestamp::from_millis(5000).unwrap();
539 let time_us = time_ms.convert(Timescale::MICRO).unwrap();
540 assert_eq!(time_us.scale(), Timescale::MICRO);
541 assert_eq!(time_us.as_micros(), 5_000_000);
542 }
543
544 #[test]
545 fn test_convert_to_coarser() {
546 let time_ms = Timestamp::from_millis(5000).unwrap();
547 let time_s = time_ms.convert(Timescale::SECOND).unwrap();
548 assert_eq!(time_s.scale(), Timescale::SECOND);
549 assert_eq!(time_s.as_secs(), 5);
550 }
551
552 #[test]
553 fn test_convert_precision_loss() {
554 let time_ms = Timestamp::from_millis(1234).unwrap();
556 let time_s = time_ms.convert(Timescale::SECOND).unwrap();
557 assert_eq!(time_s.as_secs(), 1);
558 }
559
560 #[test]
561 fn test_convert_roundtrip() {
562 let original = Timestamp::from_millis(5000).unwrap();
563 let as_micros = original.convert(Timescale::MICRO).unwrap();
564 let back = as_micros.convert(Timescale::MILLI).unwrap();
565 assert_eq!(original.value(), back.value());
566 assert_eq!(original.scale(), back.scale());
567 }
568
569 #[test]
570 fn test_convert_same_scale() {
571 let time = Timestamp::from_millis(5000).unwrap();
572 let converted = time.convert(Timescale::MILLI).unwrap();
573 assert_eq!(time, converted);
574 }
575
576 #[test]
577 fn test_add_same_scale() {
578 let a = Timestamp::from_millis(1000).unwrap();
579 let b = Timestamp::from_millis(2000).unwrap();
580 let c = a.checked_add(b).unwrap();
581 assert_eq!(c.as_millis(), 3000);
582 assert_eq!(c.scale(), Timescale::MILLI);
583 }
584
585 #[test]
586 fn test_add_mismatched_scale() {
587 let a = Timestamp::from_millis(1000).unwrap();
588 let b = Timestamp::from_micros(1000).unwrap();
589 assert!(a.checked_add(b).is_err());
590 }
591
592 #[test]
593 fn test_new_const_matches_fallible() {
594 const C: Timestamp = Timestamp::new_const(42, Timescale::MICRO);
595 assert_eq!(C, Timestamp::new(42, Timescale::MICRO).unwrap());
596 }
597
598 #[test]
599 fn test_zero_is_scale_aware() {
600 assert!(Timestamp::ZERO.is_zero());
603 let zero_ms = Timestamp::from_millis(0).unwrap();
604 assert!(zero_ms.is_zero());
605 assert_ne!(Timestamp::ZERO, zero_ms);
606 assert_ne!(Timestamp::ZERO.cmp(&zero_ms), std::cmp::Ordering::Equal);
607 }
608
609 #[test]
610 fn test_sub_underflow() {
611 let a = Timestamp::from_millis(1000).unwrap();
612 let b = Timestamp::from_millis(2000).unwrap();
613 assert!(a.checked_sub(b).is_err());
614 }
615
616 #[test]
617 fn test_max_same_scale() {
618 let a = Timestamp::from_secs(5).unwrap();
619 let b = Timestamp::from_secs(10).unwrap();
620 assert_eq!(a.max(b), b);
621 assert_eq!(b.max(a), b);
622 }
623
624 #[test]
625 fn test_max_cross_scale() {
626 let a = Timestamp::from_millis(1).unwrap();
628 let b = Timestamp::from_secs(1).unwrap();
629 assert_eq!(a.max(b), b);
630 }
631
632 #[test]
633 fn test_ordering_same_scale() {
634 let a = Timestamp::from_secs(1).unwrap();
635 let b = Timestamp::from_secs(2).unwrap();
636 assert!(a < b);
637 assert!(b > a);
638 assert_eq!(a, a);
639 }
640
641 #[test]
642 fn test_ordering_across_known_scales() {
643 let one_sec = Timestamp::from_secs(1).unwrap();
645 let two_ms = Timestamp::from_millis(2).unwrap();
646 assert!(one_sec > two_ms);
647 assert!(two_ms < one_sec);
648
649 let one_sec_b = Timestamp::from_millis(1000).unwrap();
652 assert_ne!(one_sec.cmp(&one_sec_b), std::cmp::Ordering::Equal);
653 assert_ne!(one_sec, one_sec_b);
654 assert_eq!(one_sec.cmp(&one_sec), std::cmp::Ordering::Equal);
655
656 let mut items = [
658 Timestamp::from_secs(2).unwrap(),
659 Timestamp::from_millis(500).unwrap(),
660 Timestamp::from_micros(1_500_000).unwrap(),
661 ];
662 items.sort();
663 assert_eq!(items[0], Timestamp::from_millis(500).unwrap());
664 assert_eq!(items[1], Timestamp::from_micros(1_500_000).unwrap());
665 assert_eq!(items[2], Timestamp::from_secs(2).unwrap());
666 }
667
668 #[test]
669 fn test_duration_conversion() {
670 let duration = std::time::Duration::from_secs(5);
671 let time: Timestamp = duration.try_into().unwrap();
672 assert_eq!(time.scale(), Timescale::NANO);
673 assert_eq!(time.as_secs(), 5);
674
675 let duration_back: std::time::Duration = time.into();
676 assert_eq!(duration_back.as_secs(), 5);
677 }
678
679 #[test]
680 fn test_debug_format_units() {
681 let t = Timestamp::from_millis(100_000).unwrap();
682 assert_eq!(format!("{:?}", t), "100s");
683
684 let t = Timestamp::from_millis(100).unwrap();
685 assert_eq!(format!("{:?}", t), "100ms");
686
687 let t = Timestamp::from_micros(1500).unwrap();
688 assert_eq!(format!("{:?}", t), "1500µs");
689
690 let t = Timestamp::from_micros(1000).unwrap();
691 assert_eq!(format!("{:?}", t), "1ms");
692 }
693
694 #[test]
695 fn test_new() {
696 let t = Timestamp::new(5000, Timescale::MILLI).unwrap();
697 assert_eq!(t.value(), 5000);
698 assert_eq!(t.scale(), Timescale::MILLI);
699 assert_eq!(t.as_millis(), 5000);
700 }
701
702 #[test]
703 fn test_custom_scale_convert() {
704 let scale_60 = Timescale::new(60).unwrap();
706 let t = Timestamp::new(120, scale_60)
707 .unwrap()
708 .convert(Timescale::MILLI)
709 .unwrap();
710 assert_eq!(t.scale(), Timescale::MILLI);
711 assert_eq!(t.as_millis(), 2000);
712 }
713}