1#![doc = include_str!("../README.md")]
2#![cfg_attr(not(test), no_std)]
3#![cfg_attr(docsrs, feature(doc_cfg))]
4#![cfg_attr(docsrs, allow(unused_attributes))]
5#![deny(missing_docs)]
6#![forbid(unsafe_code)]
7
8use core::{
9 cmp::Ordering,
10 hash::{Hash, Hasher},
11 num::NonZeroU32,
12 time::Duration,
13};
14
15#[derive(Debug, Clone, Copy, Eq)]
28pub struct Timebase {
29 num: u32,
30 den: NonZeroU32,
31}
32
33impl Timebase {
34 #[cfg_attr(not(tarpaulin), inline(always))]
36 pub const fn new(num: u32, den: NonZeroU32) -> Self {
37 Self { num, den }
38 }
39
40 #[cfg_attr(not(tarpaulin), inline(always))]
42 pub const fn num(&self) -> u32 {
43 self.num
44 }
45
46 #[cfg_attr(not(tarpaulin), inline(always))]
48 pub const fn den(&self) -> NonZeroU32 {
49 self.den
50 }
51
52 #[cfg_attr(not(tarpaulin), inline(always))]
54 pub const fn with_num(mut self, num: u32) -> Self {
55 self.set_num(num);
56 self
57 }
58
59 #[cfg_attr(not(tarpaulin), inline(always))]
61 pub const fn with_den(mut self, den: NonZeroU32) -> Self {
62 self.set_den(den);
63 self
64 }
65
66 #[cfg_attr(not(tarpaulin), inline(always))]
68 pub const fn set_num(&mut self, num: u32) -> &mut Self {
69 self.num = num;
70 self
71 }
72
73 #[cfg_attr(not(tarpaulin), inline(always))]
75 pub const fn set_den(&mut self, den: NonZeroU32) -> &mut Self {
76 self.den = den;
77 self
78 }
79
80 #[cfg_attr(not(tarpaulin), inline(always))]
92 pub const fn rescale_pts(pts: i64, from: Self, to: Self) -> i64 {
93 assert!(to.num != 0, "target timebase numerator must be non-zero");
94 let numerator = (pts as i128) * (from.num as i128) * (to.den.get() as i128);
97 let denominator = (from.den.get() as i128) * (to.num as i128);
98 let q = numerator / denominator;
99 if q > i64::MAX as i128 {
100 i64::MAX
101 } else if q < i64::MIN as i128 {
102 i64::MIN
103 } else {
104 q as i64
105 }
106 }
107
108 #[cfg_attr(not(tarpaulin), inline(always))]
116 pub const fn rescale(&self, pts: i64, to: Self) -> i64 {
117 Self::rescale_pts(pts, *self, to)
118 }
119
120 #[cfg_attr(not(tarpaulin), inline(always))]
136 pub const fn frames_to_duration(&self, frames: u32) -> Duration {
137 let num = self.num as u128;
139 let den = self.den.get() as u128;
140 assert!(num != 0, "frame rate numerator must be non-zero");
141 let total_ns = (frames as u128) * den * 1_000_000_000 / num;
142 let secs = (total_ns / 1_000_000_000) as u64;
143 let nanos = (total_ns % 1_000_000_000) as u32;
144 Duration::new(secs, nanos)
145 }
146
147 #[cfg_attr(not(tarpaulin), inline(always))]
154 pub const fn duration_to_pts(&self, d: Duration) -> i64 {
155 let num = self.num as u128;
156 if num == 0 {
157 return 0;
158 }
159 let den = self.den.get() as u128;
160 let ns = d.as_nanos();
162 let pts = ns * den / (num * 1_000_000_000);
163 if pts > i64::MAX as u128 {
164 i64::MAX
165 } else {
166 pts as i64
167 }
168 }
169}
170
171impl PartialEq for Timebase {
172 #[cfg_attr(not(tarpaulin), inline(always))]
173 fn eq(&self, other: &Self) -> bool {
174 (self.num as u64) * (other.den.get() as u64) == (other.num as u64) * (self.den.get() as u64)
176 }
177}
178
179impl Hash for Timebase {
180 fn hash<H: Hasher>(&self, state: &mut H) {
181 let d = self.den.get();
182 let g = gcd_u32(self.num, d);
184 (self.num / g).hash(state);
185 (d / g).hash(state);
186 }
187}
188
189impl Ord for Timebase {
190 #[cfg_attr(not(tarpaulin), inline(always))]
191 fn cmp(&self, other: &Self) -> Ordering {
192 let lhs = (self.num as u64) * (other.den.get() as u64);
193 let rhs = (other.num as u64) * (self.den.get() as u64);
194 lhs.cmp(&rhs)
195 }
196}
197
198impl PartialOrd for Timebase {
199 #[cfg_attr(not(tarpaulin), inline(always))]
200 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
201 Some(self.cmp(other))
202 }
203}
204
205#[derive(Debug, Clone, Copy)]
217pub struct Timestamp {
218 pts: i64,
219 timebase: Timebase,
220}
221
222impl Timestamp {
223 #[cfg_attr(not(tarpaulin), inline(always))]
225 pub const fn new(pts: i64, timebase: Timebase) -> Self {
226 Self { pts, timebase }
227 }
228
229 #[cfg_attr(not(tarpaulin), inline(always))]
234 pub const fn pts(&self) -> i64 {
235 self.pts
236 }
237
238 #[cfg_attr(not(tarpaulin), inline(always))]
240 pub const fn timebase(&self) -> Timebase {
241 self.timebase
242 }
243
244 #[cfg_attr(not(tarpaulin), inline(always))]
246 pub const fn with_pts(mut self, pts: i64) -> Self {
247 self.set_pts(pts);
248 self
249 }
250
251 #[cfg_attr(not(tarpaulin), inline(always))]
253 pub const fn set_pts(&mut self, pts: i64) -> &mut Self {
254 self.pts = pts;
255 self
256 }
257
258 #[cfg_attr(not(tarpaulin), inline(always))]
263 pub const fn rescale_to(self, target: Timebase) -> Self {
264 Self {
265 pts: self.timebase.rescale(self.pts, target),
266 timebase: target,
267 }
268 }
269
270 #[cfg_attr(not(tarpaulin), inline(always))]
278 pub const fn saturating_sub_duration(self, d: Duration) -> Self {
279 let units = self.timebase.duration_to_pts(d);
280 Self::new(self.pts.saturating_sub(units), self.timebase)
281 }
282
283 #[cfg_attr(not(tarpaulin), inline(always))]
289 pub const fn cmp_semantic(&self, other: &Self) -> Ordering {
290 if self.timebase.num == other.timebase.num
291 && self.timebase.den.get() == other.timebase.den.get()
292 {
293 return if self.pts < other.pts {
294 Ordering::Less
295 } else if self.pts > other.pts {
296 Ordering::Greater
297 } else {
298 Ordering::Equal
299 };
300 }
301 let lhs = (self.pts as i128) * (self.timebase.num as i128) * (other.timebase.den.get() as i128);
304 let rhs =
305 (other.pts as i128) * (other.timebase.num as i128) * (self.timebase.den.get() as i128);
306 if lhs < rhs {
307 Ordering::Less
308 } else if lhs > rhs {
309 Ordering::Greater
310 } else {
311 Ordering::Equal
312 }
313 }
314
315 #[cfg_attr(not(tarpaulin), inline(always))]
324 pub const fn duration_since(&self, earlier: &Self) -> Option<Duration> {
325 const NS_PER_SEC: i128 = 1_000_000_000;
326
327 let self_den = self.timebase.den.get();
330 let earlier_den = earlier.timebase.den.get();
331
332 let mut a = self_den;
333 let mut b = earlier_den;
334 while b != 0 {
335 let r = a % b;
336 a = b;
337 b = r;
338 }
339 let gcd = a as i128;
340
341 let self_scale = (earlier_den as i128) / gcd;
342 let earlier_scale = (self_den as i128) / gcd;
343 let common_den = (self_den as i128) * self_scale; let diff_num = (self.pts as i128) * (self.timebase.num as i128) * self_scale
347 - (earlier.pts as i128) * (earlier.timebase.num as i128) * earlier_scale;
348 if diff_num < 0 {
349 return None;
350 }
351
352 let secs_i128 = diff_num / common_den;
354 if secs_i128 > u64::MAX as i128 {
355 return Some(Duration::MAX);
356 }
357 let rem = diff_num % common_den;
358 let nanos = (rem * NS_PER_SEC / common_den) as u32;
359 Some(Duration::new(secs_i128 as u64, nanos))
360 }
361}
362
363impl PartialEq for Timestamp {
364 #[cfg_attr(not(tarpaulin), inline(always))]
365 fn eq(&self, other: &Self) -> bool {
366 self.cmp_semantic(other).is_eq()
367 }
368}
369impl Eq for Timestamp {}
370
371impl Hash for Timestamp {
372 #[cfg_attr(not(tarpaulin), inline(always))]
373 fn hash<H: Hasher>(&self, state: &mut H) {
374 let n: i128 = (self.pts as i128) * (self.timebase.num as i128);
376 let d: u128 = self.timebase.den.get() as u128;
377 let g = gcd_u128(n.unsigned_abs(), d) as i128;
379 let rn = n / g;
380 let rd = (d as i128) / g;
381 rn.hash(state);
382 rd.hash(state);
383 }
384}
385
386impl Ord for Timestamp {
387 #[cfg_attr(not(tarpaulin), inline(always))]
388 fn cmp(&self, other: &Self) -> Ordering {
389 self.cmp_semantic(other)
390 }
391}
392impl PartialOrd for Timestamp {
393 #[cfg_attr(not(tarpaulin), inline(always))]
394 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
395 Some(self.cmp(other))
396 }
397}
398
399#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
409pub struct TimeRange {
410 start: i64,
411 end: i64,
412 timebase: Timebase,
413}
414
415impl TimeRange {
416 #[cfg_attr(not(tarpaulin), inline(always))]
418 pub const fn new(start: i64, end: i64, timebase: Timebase) -> Self {
419 Self {
420 start,
421 end,
422 timebase,
423 }
424 }
425
426 #[cfg_attr(not(tarpaulin), inline(always))]
428 pub const fn instant(ts: Timestamp) -> Self {
429 Self {
430 start: ts.pts(),
431 end: ts.pts(),
432 timebase: ts.timebase(),
433 }
434 }
435
436 #[cfg_attr(not(tarpaulin), inline(always))]
438 pub const fn start_pts(&self) -> i64 {
439 self.start
440 }
441
442 #[cfg_attr(not(tarpaulin), inline(always))]
444 pub const fn end_pts(&self) -> i64 {
445 self.end
446 }
447
448 #[cfg_attr(not(tarpaulin), inline(always))]
450 pub const fn timebase(&self) -> Timebase {
451 self.timebase
452 }
453
454 #[cfg_attr(not(tarpaulin), inline(always))]
456 pub const fn start(&self) -> Timestamp {
457 Timestamp::new(self.start, self.timebase)
458 }
459
460 #[cfg_attr(not(tarpaulin), inline(always))]
462 pub const fn end(&self) -> Timestamp {
463 Timestamp::new(self.end, self.timebase)
464 }
465
466 #[cfg_attr(not(tarpaulin), inline(always))]
468 pub const fn with_start(mut self, val: i64) -> Self {
469 self.start = val;
470 self
471 }
472
473 #[cfg_attr(not(tarpaulin), inline(always))]
475 pub const fn set_start(&mut self, val: i64) -> &mut Self {
476 self.start = val;
477 self
478 }
479
480 #[cfg_attr(not(tarpaulin), inline(always))]
482 pub const fn with_end(mut self, val: i64) -> Self {
483 self.end = val;
484 self
485 }
486
487 #[cfg_attr(not(tarpaulin), inline(always))]
489 pub const fn set_end(&mut self, val: i64) -> &mut Self {
490 self.end = val;
491 self
492 }
493
494 #[cfg_attr(not(tarpaulin), inline(always))]
496 pub const fn is_instant(&self) -> bool {
497 self.start == self.end
498 }
499
500 #[cfg_attr(not(tarpaulin), inline(always))]
503 pub const fn duration(&self) -> Option<Duration> {
504 self.end().duration_since(&self.start())
505 }
506
507 #[cfg_attr(not(tarpaulin), inline(always))]
514 pub const fn interpolate(&self, t: f64) -> Timestamp {
515 let t = t.clamp(0.0, 1.0);
516 let delta = self.end.saturating_sub(self.start);
517 let offset = (delta as f64 * t) as i64;
518 Timestamp::new(self.start.saturating_add(offset), self.timebase)
519 }
520}
521
522#[cfg_attr(not(tarpaulin), inline(always))]
523const fn gcd_u32(mut a: u32, mut b: u32) -> u32 {
524 while b != 0 {
525 let t = b;
526 b = a % b;
527 a = t;
528 }
529 a
530}
531
532#[cfg_attr(not(tarpaulin), inline(always))]
533const fn gcd_u128(mut a: u128, mut b: u128) -> u128 {
534 while b != 0 {
535 let t = b;
536 b = a % b;
537 a = t;
538 }
539 a
540}
541
542#[cfg(test)]
543mod tests {
544 use super::*;
545
546 const fn nz(n: u32) -> NonZeroU32 {
547 match NonZeroU32::new(n) {
548 Some(v) => v,
549 None => panic!("zero"),
550 }
551 }
552
553 fn hash_of<T: Hash>(v: &T) -> u64 {
554 use std::collections::hash_map::DefaultHasher;
555 let mut h = DefaultHasher::new();
556 v.hash(&mut h);
557 h.finish()
558 }
559
560 #[test]
561 fn rescale_identity() {
562 let tb = Timebase::new(1, nz(1000));
563 assert_eq!(Timebase::rescale_pts(42, tb, tb), 42);
564 assert_eq!(tb.rescale(42, tb), 42);
565 }
566
567 #[test]
568 fn rescale_between_timebases() {
569 let ms = Timebase::new(1, nz(1000));
570 let mpeg = Timebase::new(1, nz(90_000));
571 assert_eq!(Timebase::rescale_pts(1000, ms, mpeg), 90_000);
572 assert_eq!(ms.rescale(1000, mpeg), 90_000);
573 assert_eq!(mpeg.rescale(90_000, ms), 1000);
574 }
575
576 #[test]
577 fn rescale_rounds_toward_zero() {
578 let from = Timebase::new(1, nz(1000));
579 let to = Timebase::new(1, nz(3));
580 assert_eq!(from.rescale(1, to), 0);
581 assert_eq!(from.rescale(-1, to), 0);
582 }
583
584 #[test]
585 fn rescale_saturates_on_i64_overflow() {
586 let from = Timebase::new(u32::MAX, nz(1));
591 let to = Timebase::new(1, nz(u32::MAX));
592 assert_eq!(from.rescale(1_000_000, to), i64::MAX);
593 assert_eq!(from.rescale(-1_000_000, to), i64::MIN);
594 }
595
596 #[test]
597 fn timebase_eq_is_semantic() {
598 let a = Timebase::new(1, nz(2));
600 let b = Timebase::new(2, nz(4));
601 let c = Timebase::new(3, nz(6));
602 assert_eq!(a, b);
603 assert_eq!(b, c);
604 assert_eq!(a, c);
605 let d = Timebase::new(1, nz(3));
607 assert_ne!(a, d);
608 }
609
610 #[test]
611 fn timebase_hash_matches_eq() {
612 let a = Timebase::new(1, nz(2));
613 let b = Timebase::new(2, nz(4));
614 let c = Timebase::new(3, nz(6));
615 assert_eq!(hash_of(&a), hash_of(&b));
616 assert_eq!(hash_of(&b), hash_of(&c));
617 }
618
619 #[test]
620 fn timebase_ord_is_numeric() {
621 let third = Timebase::new(1, nz(3));
622 let half = Timebase::new(1, nz(2));
623 let two_thirds = Timebase::new(2, nz(3));
624 let one = Timebase::new(1, nz(1));
625 assert!(third < half);
626 assert!(half < two_thirds);
627 assert!(two_thirds < one);
628 assert!(one > third);
630 }
631
632 #[test]
633 fn timebase_num_zero() {
634 let a = Timebase::new(0, nz(3));
636 let b = Timebase::new(0, nz(5));
637 assert_eq!(a, b);
638 assert_eq!(hash_of(&a), hash_of(&b));
639 assert!(a < Timebase::new(1, nz(1_000_000)));
640 }
641
642 #[test]
643 fn timestamp_cmp_same_timebase() {
644 let tb = Timebase::new(1, nz(1000));
645 let a = Timestamp::new(100, tb);
646 let b = Timestamp::new(200, tb);
647 assert!(a < b);
648 assert!(b > a);
649 assert_eq!(a, a);
650 assert_eq!(a.cmp(&b), Ordering::Less);
651 }
652
653 #[test]
654 fn timestamp_cmp_cross_timebase() {
655 let a = Timestamp::new(1000, Timebase::new(1, nz(1000)));
656 let b = Timestamp::new(90_000, Timebase::new(1, nz(90_000)));
657 assert_eq!(a, b);
658 assert_eq!(a.cmp(&b), Ordering::Equal);
659
660 let c = Timestamp::new(500, Timebase::new(1, nz(1000)));
661 assert!(c < a);
662 assert!(a > c);
663 }
664
665 #[test]
666 fn timestamp_hash_matches_semantic_eq() {
667 let a = Timestamp::new(1000, Timebase::new(1, nz(1000)));
668 let b = Timestamp::new(90_000, Timebase::new(1, nz(90_000)));
669 let c = Timestamp::new(2000, Timebase::new(1, nz(2000))); assert_eq!(a, b);
671 assert_eq!(hash_of(&a), hash_of(&b));
672 assert_eq!(hash_of(&a), hash_of(&c));
673 }
674
675 #[test]
676 fn timestamp_hash_negative_pts() {
677 let a = Timestamp::new(-500, Timebase::new(1, nz(1000)));
679 let b = Timestamp::new(-45_000, Timebase::new(1, nz(90_000)));
680 assert_eq!(a, b);
681 assert_eq!(hash_of(&a), hash_of(&b));
682 }
683
684 #[test]
685 fn rescale_to_preserves_instant() {
686 let ms = Timebase::new(1, nz(1000));
687 let mpeg = Timebase::new(1, nz(90_000));
688 let a = Timestamp::new(1000, ms);
689 let b = a.rescale_to(mpeg);
690 assert_eq!(b.pts(), 90_000);
691 assert_eq!(b.timebase(), mpeg);
692 assert_eq!(a, b);
693 }
694
695 #[test]
696 fn duration_since_same_timebase() {
697 let tb = Timebase::new(1, nz(1000));
698 let a = Timestamp::new(1500, tb);
699 let b = Timestamp::new(500, tb);
700 assert_eq!(a.duration_since(&b), Some(Duration::from_millis(1000)));
701 assert_eq!(b.duration_since(&a), None);
702 }
703
704 #[test]
705 fn duration_since_cross_timebase() {
706 let a = Timestamp::new(1000, Timebase::new(1, nz(1000)));
707 let b = Timestamp::new(45_000, Timebase::new(1, nz(90_000)));
708 assert_eq!(a.duration_since(&b), Some(Duration::from_millis(500)));
709 }
710
711 #[test]
712 fn duration_since_saturates_to_duration_max_on_overflow() {
713 let tb = Timebase::new(u32::MAX, nz(1));
717 let huge = Timestamp::new(i64::MAX, tb);
718 let zero = Timestamp::new(0, tb);
719 assert_eq!(huge.duration_since(&zero), Some(Duration::MAX));
720 }
721
722 #[test]
723 fn frames_to_duration_integer_fps() {
724 let fps30 = Timebase::new(30, nz(1));
725 assert_eq!(fps30.frames_to_duration(15), Duration::from_millis(500));
726 assert_eq!(fps30.frames_to_duration(30), Duration::from_secs(1));
727 assert_eq!(fps30.frames_to_duration(0), Duration::ZERO);
728 }
729
730 #[test]
731 fn frames_to_duration_ntsc() {
732 let ntsc = Timebase::new(30_000, nz(1001));
734 assert_eq!(ntsc.frames_to_duration(30_000), Duration::from_secs(1001));
735 assert_eq!(
737 ntsc.frames_to_duration(15),
738 Duration::from_nanos(500_500_000),
739 );
740 }
741
742 #[test]
743 fn time_range_basic() {
744 let tb = Timebase::new(1, nz(1000));
745 let r = TimeRange::new(100, 500, tb);
746 assert_eq!(r.start_pts(), 100);
747 assert_eq!(r.end_pts(), 500);
748 assert_eq!(r.timebase(), tb);
749 assert_eq!(r.start(), Timestamp::new(100, tb));
750 assert_eq!(r.end(), Timestamp::new(500, tb));
751 assert!(!r.is_instant());
752 assert_eq!(r.duration(), Some(Duration::from_millis(400)));
753 assert_eq!(r.interpolate(0.0).pts(), 100);
755 assert_eq!(r.interpolate(1.0).pts(), 500);
756 assert_eq!(r.interpolate(0.5).pts(), 300);
757 assert_eq!(r.interpolate(-1.0).pts(), 100);
759 assert_eq!(r.interpolate(2.0).pts(), 500);
760 }
761
762 #[test]
763 fn time_range_instant() {
764 let tb = Timebase::new(1, nz(1000));
765 let ts = Timestamp::new(123, tb);
766 let r = TimeRange::instant(ts);
767 assert!(r.is_instant());
768 assert_eq!(r.start_pts(), 123);
769 assert_eq!(r.end_pts(), 123);
770 assert_eq!(r.duration(), Some(Duration::ZERO));
771 }
772
773 #[test]
779 fn timebase_accessors_and_builders() {
780 let tb = Timebase::new(30_000, nz(1001));
781 assert_eq!(tb.num(), 30_000);
782 assert_eq!(tb.den(), nz(1001));
783
784 let tb2 = tb.with_num(48_000).with_den(nz(1));
786 assert_eq!(tb2.num(), 48_000);
787 assert_eq!(tb2.den(), nz(1));
788
789 let mut tb3 = Timebase::new(1, nz(1000));
791 tb3.set_num(25).set_den(nz(2));
792 assert_eq!(tb3.num(), 25);
793 assert_eq!(tb3.den(), nz(2));
794 }
795
796 #[test]
797 fn duration_to_pts_happy_path_and_edge_cases() {
798 let ms = Timebase::new(1, nz(1000));
800 assert_eq!(ms.duration_to_pts(Duration::from_millis(1500)), 1500);
801 assert_eq!(ms.duration_to_pts(Duration::ZERO), 0);
802
803 let mpegts = Timebase::new(1, nz(90_000));
805 assert_eq!(mpegts.duration_to_pts(Duration::from_secs(2)), 180_000,);
806
807 let degenerate = Timebase::new(0, nz(1));
809 assert_eq!(degenerate.duration_to_pts(Duration::from_secs(1)), 0,);
810
811 let fps1 = Timebase::new(1, nz(1));
816 let huge = Duration::new(u64::MAX, 0);
817 assert_eq!(fps1.duration_to_pts(huge), i64::MAX);
818 }
819
820 #[test]
821 fn timestamp_accessors_and_builders() {
822 let tb = Timebase::new(1, nz(1000));
823 let mut ts = Timestamp::new(42, tb);
824 assert_eq!(ts.pts(), 42);
825 assert_eq!(ts.timebase(), tb);
826
827 let ts2 = ts.with_pts(777);
829 assert_eq!(ts2.pts(), 777);
830
831 ts.set_pts(-5).set_pts(-6);
833 assert_eq!(ts.pts(), -6);
834 }
835
836 #[test]
837 fn cmp_semantic_exercises_all_branches() {
838 let tb_a = Timebase::new(1, nz(1000)); let tb_b = Timebase::new(1, nz(90_000)); let a = Timestamp::new(100, tb_a);
843 let b = Timestamp::new(200, tb_a);
844 assert_eq!(a.cmp_semantic(&b), Ordering::Less);
845 assert_eq!(b.cmp_semantic(&a), Ordering::Greater);
846 assert_eq!(a.cmp_semantic(&a), Ordering::Equal);
847
848 let one_second_ms = Timestamp::new(1000, tb_a);
850 let one_second_mpg = Timestamp::new(90_000, tb_b);
851 let half_second_ms = Timestamp::new(500, tb_a);
852 let two_seconds_mpg = Timestamp::new(180_000, tb_b);
853 assert_eq!(half_second_ms.cmp_semantic(&one_second_mpg), Ordering::Less,);
854 assert_eq!(
855 two_seconds_mpg.cmp_semantic(&one_second_ms),
856 Ordering::Greater,
857 );
858 assert_eq!(one_second_ms.cmp_semantic(&one_second_mpg), Ordering::Equal,);
859 }
860
861 #[test]
862 fn saturating_sub_duration_saturates() {
863 let tb = Timebase::new(1, nz(1000));
864 let near_floor = Timestamp::new(i64::MIN + 10, tb);
867 let shifted = near_floor.saturating_sub_duration(Duration::from_secs(1));
868 assert_eq!(shifted.pts(), i64::MIN);
869
870 let ts = Timestamp::new(1500, tb);
872 let shifted = ts.saturating_sub_duration(Duration::from_millis(500));
873 assert_eq!(shifted.pts(), 1000);
874 }
875
876 #[test]
877 fn time_range_builders_and_setters() {
878 let tb = Timebase::new(1, nz(1000));
879 let r = TimeRange::new(0, 0, tb);
880
881 let r2 = r.with_start(100).with_end(500);
883 assert_eq!(r2.start_pts(), 100);
884 assert_eq!(r2.end_pts(), 500);
885
886 let mut r3 = TimeRange::new(0, 0, tb);
888 r3.set_start(10).set_end(20);
889 assert_eq!(r3.start_pts(), 10);
890 assert_eq!(r3.end_pts(), 20);
891
892 let reversed = TimeRange::new(500, 100, tb);
894 assert!(reversed.duration().is_none());
895 }
896}