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 = Self(NonZero::<u64>::MIN);
26 pub const MILLI: Self = match NonZero::new(1_000) {
28 Some(n) => Self(n),
29 None => unreachable!(),
30 };
31 pub const MICRO: Self = match NonZero::new(1_000_000) {
34 Some(n) => Self(n),
35 None => unreachable!(),
36 };
37 pub const NANO: Self = match NonZero::new(1_000_000_000) {
39 Some(n) => Self(n),
40 None => unreachable!(),
41 };
42
43 pub const fn new(units_per_second: u64) -> Result<Self, TimeOverflow> {
48 if VarInt::from_u64(units_per_second).is_none() {
51 return Err(TimeOverflow);
52 }
53 match NonZero::new(units_per_second) {
54 Some(n) => Ok(Self(n)),
55 None => Err(TimeOverflow),
56 }
57 }
58
59 pub const fn as_u64(self) -> u64 {
61 self.0.get()
62 }
63}
64
65impl TryFrom<u64> for Timescale {
66 type Error = TimeOverflow;
67
68 fn try_from(units_per_second: u64) -> Result<Self, Self::Error> {
69 Self::new(units_per_second)
70 }
71}
72
73impl From<NonZero<u64>> for Timescale {
74 fn from(units_per_second: NonZero<u64>) -> Self {
75 Self(units_per_second)
76 }
77}
78
79impl From<Timescale> for u64 {
80 fn from(scale: Timescale) -> Self {
81 scale.0.get()
82 }
83}
84
85impl From<Timescale> for NonZero<u64> {
86 fn from(scale: Timescale) -> Self {
87 scale.0
88 }
89}
90
91impl Default for Timescale {
92 fn default() -> Self {
96 Self::MILLI
97 }
98}
99
100impl std::fmt::Debug for Timescale {
101 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102 match *self {
103 Self::SECOND => write!(f, "Timescale::SECOND"),
104 Self::MILLI => write!(f, "Timescale::MILLI"),
105 Self::MICRO => write!(f, "Timescale::MICRO"),
106 Self::NANO => write!(f, "Timescale::NANO"),
107 Self(n) => write!(f, "Timescale({n})"),
108 }
109 }
110}
111
112impl std::fmt::Display for Timescale {
113 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114 write!(f, "{}", self.0)
115 }
116}
117
118#[derive(Clone, Copy, PartialEq, Eq, Hash)]
151pub struct Timestamp {
152 value: VarInt,
153 scale: Timescale,
154}
155
156impl Timestamp {
157 pub const ZERO: Self = Self::new_const(0, Timescale::SECOND);
165
166 pub const fn new(value: u64, scale: Timescale) -> Result<Self, TimeOverflow> {
169 match VarInt::from_u64(value) {
170 Some(value) => Ok(Self { value, scale }),
171 None => Err(TimeOverflow),
172 }
173 }
174
175 pub const fn new_const(value: u64, scale: Timescale) -> Self {
182 match Self::new(value, scale) {
183 Ok(time) => time,
184 Err(_) => panic!("timestamp value exceeds 2^62 - 1"),
185 }
186 }
187
188 pub fn from_scale(value: u64, units_per_second: u64) -> Result<Self, TimeOverflow> {
191 Self::new(value, Timescale::new(units_per_second)?)
192 }
193
194 pub const fn from_secs(seconds: u64) -> Result<Self, TimeOverflow> {
196 Self::new(seconds, Timescale::SECOND)
197 }
198
199 pub const fn from_millis(millis: u64) -> Result<Self, TimeOverflow> {
201 Self::new(millis, Timescale::MILLI)
202 }
203
204 pub const fn from_micros(micros: u64) -> Result<Self, TimeOverflow> {
206 Self::new(micros, Timescale::MICRO)
207 }
208
209 pub const fn from_nanos(nanos: u64) -> Result<Self, TimeOverflow> {
211 Self::new(nanos, Timescale::NANO)
212 }
213
214 pub const fn value(self) -> u64 {
216 self.value.into_inner()
217 }
218
219 pub const fn scale(self) -> Timescale {
221 self.scale
222 }
223
224 pub const fn is_zero(self) -> bool {
226 self.value.into_inner() == 0
227 }
228
229 pub const fn convert(self, new_scale: Timescale) -> Result<Self, TimeOverflow> {
232 if self.scale.0.get() == new_scale.0.get() {
233 return Ok(self);
234 }
235 match (self.value.into_inner() as u128).checked_mul(new_scale.0.get() as u128) {
236 Some(scaled) => match VarInt::from_u128(scaled / self.scale.0.get() as u128) {
237 Some(value) => Ok(Self {
238 value,
239 scale: new_scale,
240 }),
241 None => Err(TimeOverflow),
242 },
243 None => Err(TimeOverflow),
244 }
245 }
246
247 pub const fn as_scale(self, target: Timescale) -> u128 {
249 self.value.into_inner() as u128 * target.0.get() as u128 / self.scale.0.get() as u128
250 }
251
252 pub const fn as_secs(self) -> u64 {
254 self.value.into_inner() / self.scale.0.get()
255 }
256
257 pub const fn as_millis(self) -> u128 {
259 self.as_scale(Timescale::MILLI)
260 }
261
262 pub const fn as_micros(self) -> u128 {
264 self.as_scale(Timescale::MICRO)
265 }
266
267 pub const fn as_nanos(self) -> u128 {
269 self.as_scale(Timescale::NANO)
270 }
271
272 pub const fn checked_add(self, rhs: Self) -> Result<Self, TimeOverflow> {
275 if self.scale.0.get() != rhs.scale.0.get() {
276 return Err(TimeOverflow);
277 }
278 match self.value.into_inner().checked_add(rhs.value.into_inner()) {
279 Some(result) => Self::new(result, self.scale),
280 None => Err(TimeOverflow),
281 }
282 }
283
284 pub const fn checked_sub(self, rhs: Self) -> Result<Self, TimeOverflow> {
287 if self.scale.0.get() != rhs.scale.0.get() {
288 return Err(TimeOverflow);
289 }
290 match self.value.into_inner().checked_sub(rhs.value.into_inner()) {
291 Some(result) => Self::new(result, self.scale),
292 None => Err(TimeOverflow),
293 }
294 }
295
296 pub fn now() -> Self {
305 clock::now()
306 }
307}
308
309impl TryFrom<std::time::Duration> for Timestamp {
310 type Error = TimeOverflow;
311
312 fn try_from(duration: std::time::Duration) -> Result<Self, Self::Error> {
314 match VarInt::from_u128(duration.as_nanos()) {
315 Some(value) => Ok(Self {
316 value,
317 scale: Timescale::NANO,
318 }),
319 None => Err(TimeOverflow),
320 }
321 }
322}
323
324impl From<Timestamp> for std::time::Duration {
325 fn from(time: Timestamp) -> Self {
326 let nanos = time.as_nanos();
327 std::time::Duration::new(time.as_secs(), (nanos % 1_000_000_000) as u32)
328 }
329}
330
331impl std::fmt::Debug for Timestamp {
332 #[allow(clippy::manual_is_multiple_of)] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
334 let nanos = self.as_nanos();
335
336 if nanos % 1_000_000_000 == 0 {
338 write!(f, "{}s", nanos / 1_000_000_000)
339 } else if nanos % 1_000_000 == 0 {
340 write!(f, "{}ms", nanos / 1_000_000)
341 } else if nanos % 1_000 == 0 {
342 write!(f, "{}µs", nanos / 1_000)
343 } else {
344 write!(f, "{}ns", nanos)
345 }
346 }
347}
348
349impl PartialOrd for Timestamp {
350 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
351 Some(self.cmp(other))
352 }
353}
354
355impl Ord for Timestamp {
356 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
364 if self.scale.0.get() == other.scale.0.get() {
365 return self.value.cmp(&other.value);
366 }
367 let lhs = self.value.into_inner() as u128 * other.scale.0.get() as u128;
368 let rhs = other.value.into_inner() as u128 * self.scale.0.get() as u128;
369 lhs.cmp(&rhs)
370 .then_with(|| self.scale.0.get().cmp(&other.scale.0.get()))
371 .then_with(|| self.value.cmp(&other.value))
372 }
373}
374
375#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
376mod clock {
377 use std::sync::LazyLock;
378 use std::time::{SystemTime, UNIX_EPOCH};
379
380 use rand::RngExt;
381
382 use super::Timestamp;
383
384 const ANCHOR_EPOCH_SECS: u64 = 1_577_836_800;
390
391 static TIME_ANCHOR: LazyLock<(std::time::Instant, SystemTime)> = LazyLock::new(|| {
393 let jitter = std::time::Duration::from_millis(rand::rng().random_range(0..69_420));
397 (std::time::Instant::now(), SystemTime::now() - jitter)
398 });
399
400 pub(super) fn now() -> Timestamp {
401 let instant: std::time::Instant = web_async::time::Instant::now().into();
402 from_std_instant(instant)
403 }
404
405 fn from_std_instant(instant: std::time::Instant) -> Timestamp {
406 let (anchor_instant, anchor_system) = *TIME_ANCHOR;
407
408 let system = match instant.checked_duration_since(anchor_instant) {
409 Some(forward) => anchor_system + forward,
410 None => anchor_system - anchor_instant.duration_since(instant),
411 };
412
413 let epoch = UNIX_EPOCH + std::time::Duration::from_secs(ANCHOR_EPOCH_SECS);
414 let duration = system.duration_since(epoch).unwrap_or(std::time::Duration::ZERO);
417
418 Timestamp::from_millis(duration.as_millis() as u64).expect("clock is somehow past the year 2300")
419 }
420
421 impl From<std::time::Instant> for Timestamp {
422 fn from(instant: std::time::Instant) -> Self {
428 from_std_instant(instant)
429 }
430 }
431}
432
433#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
434mod clock {
435 use std::sync::LazyLock;
436
437 use rand::RngExt;
438
439 use super::Timestamp;
440
441 static TIME_ANCHOR: LazyLock<(web_async::time::Instant, std::time::Duration)> = LazyLock::new(|| {
442 let jitter = std::time::Duration::from_millis(rand::rng().random_range(1..69_420));
443 (web_async::time::Instant::now(), jitter)
444 });
445
446 pub(super) fn now() -> Timestamp {
447 let (anchor_instant, anchor_duration) = *TIME_ANCHOR;
448 let instant = web_async::time::Instant::now();
449 let duration = match instant.checked_duration_since(anchor_instant) {
450 Some(forward) => anchor_duration + forward,
451 None => anchor_duration
452 .checked_sub(anchor_instant.duration_since(instant))
453 .unwrap_or(std::time::Duration::ZERO),
454 };
455
456 Timestamp::from_millis(duration.as_millis() as u64).expect("clock is somehow past the year 2300")
457 }
458}
459
460#[cfg(test)]
461mod tests {
462 use super::*;
463
464 #[test]
465 fn test_from_secs() {
466 let time = Timestamp::from_secs(5).unwrap();
467 assert_eq!(time.scale(), Timescale::SECOND);
468 assert_eq!(time.as_secs(), 5);
469 assert_eq!(time.as_millis(), 5000);
470 assert_eq!(time.as_micros(), 5_000_000);
471 assert_eq!(time.as_nanos(), 5_000_000_000);
472 }
473
474 #[test]
475 fn test_from_millis() {
476 let time = Timestamp::from_millis(5000).unwrap();
477 assert_eq!(time.scale(), Timescale::MILLI);
478 assert_eq!(time.as_secs(), 5);
479 assert_eq!(time.as_millis(), 5000);
480 }
481
482 #[test]
483 fn test_from_micros() {
484 let time = Timestamp::from_micros(5_000_000).unwrap();
485 assert_eq!(time.scale(), Timescale::MICRO);
486 assert_eq!(time.as_secs(), 5);
487 assert_eq!(time.as_micros(), 5_000_000);
488 }
489
490 #[test]
491 fn test_from_nanos() {
492 let time = Timestamp::from_nanos(5_000_000_000).unwrap();
493 assert_eq!(time.scale(), Timescale::NANO);
494 assert_eq!(time.as_secs(), 5);
495 assert_eq!(time.as_nanos(), 5_000_000_000);
496 }
497
498 #[test]
499 fn test_timescale_new_rejects_zero_and_overflow() {
500 assert!(Timescale::new(0).is_err());
501 assert!(Timescale::new(1).is_ok());
502 assert_eq!(Timescale::new(1).unwrap(), Timescale::SECOND);
503 assert_eq!(Timescale::new(1_000).unwrap(), Timescale::MILLI);
504
505 assert!(Timescale::new(1u64 << 62).is_err());
507 assert!(Timescale::new((1u64 << 62) - 1).is_ok());
509 }
510
511 #[test]
512 fn test_convert_to_finer() {
513 let time_ms = Timestamp::from_millis(5000).unwrap();
514 let time_us = time_ms.convert(Timescale::MICRO).unwrap();
515 assert_eq!(time_us.scale(), Timescale::MICRO);
516 assert_eq!(time_us.as_micros(), 5_000_000);
517 }
518
519 #[test]
520 fn test_convert_to_coarser() {
521 let time_ms = Timestamp::from_millis(5000).unwrap();
522 let time_s = time_ms.convert(Timescale::SECOND).unwrap();
523 assert_eq!(time_s.scale(), Timescale::SECOND);
524 assert_eq!(time_s.as_secs(), 5);
525 }
526
527 #[test]
528 fn test_convert_precision_loss() {
529 let time_ms = Timestamp::from_millis(1234).unwrap();
531 let time_s = time_ms.convert(Timescale::SECOND).unwrap();
532 assert_eq!(time_s.as_secs(), 1);
533 }
534
535 #[test]
536 fn test_convert_roundtrip() {
537 let original = Timestamp::from_millis(5000).unwrap();
538 let as_micros = original.convert(Timescale::MICRO).unwrap();
539 let back = as_micros.convert(Timescale::MILLI).unwrap();
540 assert_eq!(original.value(), back.value());
541 assert_eq!(original.scale(), back.scale());
542 }
543
544 #[test]
545 fn test_convert_same_scale() {
546 let time = Timestamp::from_millis(5000).unwrap();
547 let converted = time.convert(Timescale::MILLI).unwrap();
548 assert_eq!(time, converted);
549 }
550
551 #[test]
552 fn test_add_same_scale() {
553 let a = Timestamp::from_millis(1000).unwrap();
554 let b = Timestamp::from_millis(2000).unwrap();
555 let c = a.checked_add(b).unwrap();
556 assert_eq!(c.as_millis(), 3000);
557 assert_eq!(c.scale(), Timescale::MILLI);
558 }
559
560 #[test]
561 fn test_add_mismatched_scale() {
562 let a = Timestamp::from_millis(1000).unwrap();
563 let b = Timestamp::from_micros(1000).unwrap();
564 assert!(a.checked_add(b).is_err());
565 }
566
567 #[test]
568 fn test_new_const_matches_fallible() {
569 const C: Timestamp = Timestamp::new_const(42, Timescale::MICRO);
570 assert_eq!(C, Timestamp::new(42, Timescale::MICRO).unwrap());
571 }
572
573 #[test]
574 fn test_zero_is_scale_aware() {
575 assert!(Timestamp::ZERO.is_zero());
578 let zero_ms = Timestamp::from_millis(0).unwrap();
579 assert!(zero_ms.is_zero());
580 assert_ne!(Timestamp::ZERO, zero_ms);
581 assert_ne!(Timestamp::ZERO.cmp(&zero_ms), std::cmp::Ordering::Equal);
582 }
583
584 #[test]
585 fn test_sub_underflow() {
586 let a = Timestamp::from_millis(1000).unwrap();
587 let b = Timestamp::from_millis(2000).unwrap();
588 assert!(a.checked_sub(b).is_err());
589 }
590
591 #[test]
592 fn test_max_same_scale() {
593 let a = Timestamp::from_secs(5).unwrap();
594 let b = Timestamp::from_secs(10).unwrap();
595 assert_eq!(a.max(b), b);
596 assert_eq!(b.max(a), b);
597 }
598
599 #[test]
600 fn test_max_cross_scale() {
601 let a = Timestamp::from_millis(1).unwrap();
603 let b = Timestamp::from_secs(1).unwrap();
604 assert_eq!(a.max(b), b);
605 }
606
607 #[test]
608 fn test_ordering_same_scale() {
609 let a = Timestamp::from_secs(1).unwrap();
610 let b = Timestamp::from_secs(2).unwrap();
611 assert!(a < b);
612 assert!(b > a);
613 assert_eq!(a, a);
614 }
615
616 #[test]
617 fn test_ordering_across_known_scales() {
618 let one_sec = Timestamp::from_secs(1).unwrap();
620 let two_ms = Timestamp::from_millis(2).unwrap();
621 assert!(one_sec > two_ms);
622 assert!(two_ms < one_sec);
623
624 let one_sec_b = Timestamp::from_millis(1000).unwrap();
627 assert_ne!(one_sec.cmp(&one_sec_b), std::cmp::Ordering::Equal);
628 assert_ne!(one_sec, one_sec_b);
629 assert_eq!(one_sec.cmp(&one_sec), std::cmp::Ordering::Equal);
630
631 let mut items = [
633 Timestamp::from_secs(2).unwrap(),
634 Timestamp::from_millis(500).unwrap(),
635 Timestamp::from_micros(1_500_000).unwrap(),
636 ];
637 items.sort();
638 assert_eq!(items[0], Timestamp::from_millis(500).unwrap());
639 assert_eq!(items[1], Timestamp::from_micros(1_500_000).unwrap());
640 assert_eq!(items[2], Timestamp::from_secs(2).unwrap());
641 }
642
643 #[test]
644 fn test_duration_conversion() {
645 let duration = std::time::Duration::from_secs(5);
646 let time: Timestamp = duration.try_into().unwrap();
647 assert_eq!(time.scale(), Timescale::NANO);
648 assert_eq!(time.as_secs(), 5);
649
650 let duration_back: std::time::Duration = time.into();
651 assert_eq!(duration_back.as_secs(), 5);
652 }
653
654 #[test]
655 fn test_debug_format_units() {
656 let t = Timestamp::from_millis(100_000).unwrap();
657 assert_eq!(format!("{:?}", t), "100s");
658
659 let t = Timestamp::from_millis(100).unwrap();
660 assert_eq!(format!("{:?}", t), "100ms");
661
662 let t = Timestamp::from_micros(1500).unwrap();
663 assert_eq!(format!("{:?}", t), "1500µs");
664
665 let t = Timestamp::from_micros(1000).unwrap();
666 assert_eq!(format!("{:?}", t), "1ms");
667 }
668
669 #[test]
670 fn test_new() {
671 let t = Timestamp::new(5000, Timescale::MILLI).unwrap();
672 assert_eq!(t.value(), 5000);
673 assert_eq!(t.scale(), Timescale::MILLI);
674 assert_eq!(t.as_millis(), 5000);
675 }
676
677 #[test]
678 fn test_custom_scale_convert() {
679 let scale_60 = Timescale::new(60).unwrap();
681 let t = Timestamp::new(120, scale_60)
682 .unwrap()
683 .convert(Timescale::MILLI)
684 .unwrap();
685 assert_eq!(t.scale(), Timescale::MILLI);
686 assert_eq!(t.as_millis(), 2000);
687 }
688}