Skip to main content

moq_lite/model/
time.rs

1use rand::Rng;
2
3use crate::Error;
4use crate::coding::{Decode, DecodeError, Encode, EncodeError, VarInt};
5
6use std::sync::LazyLock;
7use std::time::{SystemTime, UNIX_EPOCH};
8
9/// A timestamp representing the presentation time in milliseconds.
10///
11/// The underlying implementation supports any scale, but everything uses milliseconds by default.
12pub type Time = Timescale<1_000>;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
15#[error("time overflow")]
16pub struct TimeOverflow;
17
18/// A timestamp representing the presentation time in a given scale. ex. 1000 for milliseconds.
19///
20/// All timestamps within a track are relative, so zero for one track is not zero for another.
21/// Values are constrained to fit within a QUIC VarInt (2^62) so they can be encoded and decoded easily.
22///
23/// This is [std::time::Instant] and [std::time::Duration] merged into one type for simplicity.
24#[derive(Clone, Default, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26pub struct Timescale<const SCALE: u64>(VarInt);
27
28impl<const SCALE: u64> Timescale<SCALE> {
29	/// The maximum representable instant.
30	pub const MAX: Self = Self(VarInt::MAX);
31
32	/// The minimum representable instant.
33	pub const ZERO: Self = Self(VarInt::ZERO);
34
35	pub const fn new(value: u32) -> Self {
36		Self(VarInt::from_u32(value))
37	}
38
39	pub const fn new_u64(value: u64) -> Result<Self, TimeOverflow> {
40		match VarInt::from_u64(value) {
41			Some(varint) => Ok(Self(varint)),
42			None => Err(TimeOverflow),
43		}
44	}
45
46	/// Convert a number of seconds to a timestamp, returning an error if the timestamp would overflow.
47	pub const fn from_secs(seconds: u64) -> Result<Self, TimeOverflow> {
48		// Not using from_scale because it'll be slightly faster
49		match seconds.checked_mul(SCALE) {
50			Some(value) => Self::new_u64(value),
51			None => Err(TimeOverflow),
52		}
53	}
54
55	pub const fn from_secs_unchecked(seconds: u64) -> Self {
56		match Self::from_secs(seconds) {
57			Ok(time) => time,
58			Err(_) => panic!("time overflow"),
59		}
60	}
61
62	/// Convert a number of milliseconds to a timestamp, returning an error if the timestamp would overflow.
63	pub const fn from_millis(millis: u64) -> Result<Self, TimeOverflow> {
64		Self::from_scale(millis, 1000)
65	}
66
67	pub const fn from_millis_unchecked(millis: u64) -> Self {
68		Self::from_scale_unchecked(millis, 1000)
69	}
70
71	pub const fn from_micros(micros: u64) -> Result<Self, TimeOverflow> {
72		Self::from_scale(micros, 1_000_000)
73	}
74
75	pub const fn from_micros_unchecked(micros: u64) -> Self {
76		Self::from_scale_unchecked(micros, 1_000_000)
77	}
78
79	pub const fn from_nanos(nanos: u64) -> Result<Self, TimeOverflow> {
80		Self::from_scale(nanos, 1_000_000_000)
81	}
82
83	pub const fn from_nanos_unchecked(nanos: u64) -> Self {
84		Self::from_scale_unchecked(nanos, 1_000_000_000)
85	}
86
87	pub const fn from_scale(value: u64, scale: u64) -> Result<Self, TimeOverflow> {
88		match VarInt::from_u128(value as u128 * SCALE as u128 / scale as u128) {
89			Some(varint) => Ok(Self(varint)),
90			None => Err(TimeOverflow),
91		}
92	}
93
94	pub const fn from_scale_u128(value: u128, scale: u64) -> Result<Self, TimeOverflow> {
95		match value.checked_mul(SCALE as u128) {
96			Some(value) => match VarInt::from_u128(value / scale as u128) {
97				Some(varint) => Ok(Self(varint)),
98				None => Err(TimeOverflow),
99			},
100			None => Err(TimeOverflow),
101		}
102	}
103
104	pub const fn from_scale_unchecked(value: u64, scale: u64) -> Self {
105		match Self::from_scale(value, scale) {
106			Ok(time) => time,
107			Err(_) => panic!("time overflow"),
108		}
109	}
110
111	/// Get the timestamp as seconds.
112	pub const fn as_secs(self) -> u64 {
113		self.0.into_inner() / SCALE
114	}
115
116	/// Get the timestamp as milliseconds.
117	//
118	// This returns a u128 to avoid a possible overflow when SCALE < 250
119	pub const fn as_millis(self) -> u128 {
120		self.as_scale(1000)
121	}
122
123	/// Get the timestamp as microseconds.
124	pub const fn as_micros(self) -> u128 {
125		self.as_scale(1_000_000)
126	}
127
128	/// Get the timestamp as nanoseconds.
129	pub const fn as_nanos(self) -> u128 {
130		self.as_scale(1_000_000_000)
131	}
132
133	pub const fn as_scale(self, scale: u64) -> u128 {
134		self.0.into_inner() as u128 * scale as u128 / SCALE as u128
135	}
136
137	/// Get the maximum of two timestamps.
138	pub const fn max(self, other: Self) -> Self {
139		if self.0.into_inner() > other.0.into_inner() {
140			self
141		} else {
142			other
143		}
144	}
145
146	pub const fn checked_add(self, rhs: Self) -> Result<Self, TimeOverflow> {
147		let lhs = self.0.into_inner();
148		let rhs = rhs.0.into_inner();
149		match lhs.checked_add(rhs) {
150			Some(result) => Self::new_u64(result),
151			None => Err(TimeOverflow),
152		}
153	}
154
155	pub const fn checked_sub(self, rhs: Self) -> Result<Self, TimeOverflow> {
156		let lhs = self.0.into_inner();
157		let rhs = rhs.0.into_inner();
158		match lhs.checked_sub(rhs) {
159			Some(result) => Self::new_u64(result),
160			None => Err(TimeOverflow),
161		}
162	}
163
164	pub const fn is_zero(self) -> bool {
165		self.0.into_inner() == 0
166	}
167
168	pub fn now() -> Self {
169		// We use tokio so it can be stubbed for testing.
170		tokio::time::Instant::now().into()
171	}
172
173	/// Convert this timestamp to a different scale.
174	///
175	/// This allows converting between different TimeScale types, for example from milliseconds to microseconds.
176	/// Note that converting to a coarser scale may lose precision due to integer division.
177	pub const fn convert<const NEW_SCALE: u64>(self) -> Result<Timescale<NEW_SCALE>, TimeOverflow> {
178		let value = self.0.into_inner();
179		// Convert from SCALE to NEW_SCALE: value * NEW_SCALE / SCALE
180		match (value as u128).checked_mul(NEW_SCALE as u128) {
181			Some(v) => match v.checked_div(SCALE as u128) {
182				Some(v) => match VarInt::from_u128(v) {
183					Some(varint) => Ok(Timescale(varint)),
184					None => Err(TimeOverflow),
185				},
186				None => Err(TimeOverflow),
187			},
188			None => Err(TimeOverflow),
189		}
190	}
191
192	pub fn encode<W: bytes::BufMut>(&self, w: &mut W) -> Result<(), EncodeError> {
193		self.0.encode(w, ())?;
194		Ok(())
195	}
196
197	pub fn decode<R: bytes::Buf>(r: &mut R) -> Result<Self, Error> {
198		let v = VarInt::decode(r, ())?;
199		Ok(Self(v))
200	}
201}
202
203impl<const SCALE: u64> TryFrom<std::time::Duration> for Timescale<SCALE> {
204	type Error = TimeOverflow;
205
206	fn try_from(duration: std::time::Duration) -> Result<Self, Self::Error> {
207		Self::from_scale_u128(duration.as_nanos(), 1_000_000_000)
208	}
209}
210
211impl<const SCALE: u64> From<Timescale<SCALE>> for std::time::Duration {
212	fn from(time: Timescale<SCALE>) -> Self {
213		std::time::Duration::new(time.as_secs(), (time.as_nanos() % 1_000_000_000) as u32)
214	}
215}
216
217impl<const SCALE: u64> std::fmt::Debug for Timescale<SCALE> {
218	#[allow(clippy::manual_is_multiple_of)] // is_multiple_of is unstable in Rust 1.85
219	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
220		let nanos = self.as_nanos();
221
222		// Choose the largest unit where we don't need decimal places
223		// Check from largest to smallest unit
224		if nanos % 1_000_000_000 == 0 {
225			write!(f, "{}s", nanos / 1_000_000_000)
226		} else if nanos % 1_000_000 == 0 {
227			write!(f, "{}ms", nanos / 1_000_000)
228		} else if nanos % 1_000 == 0 {
229			write!(f, "{}µs", nanos / 1_000)
230		} else {
231			write!(f, "{}ns", nanos)
232		}
233	}
234}
235
236impl<const SCALE: u64> std::ops::Add for Timescale<SCALE> {
237	type Output = Self;
238
239	fn add(self, rhs: Self) -> Self {
240		self.checked_add(rhs).expect("time overflow")
241	}
242}
243
244impl<const SCALE: u64> std::ops::AddAssign for Timescale<SCALE> {
245	fn add_assign(&mut self, rhs: Self) {
246		*self = *self + rhs;
247	}
248}
249
250impl<const SCALE: u64> std::ops::Sub for Timescale<SCALE> {
251	type Output = Self;
252
253	fn sub(self, rhs: Self) -> Self {
254		self.checked_sub(rhs).expect("time overflow")
255	}
256}
257
258impl<const SCALE: u64> std::ops::SubAssign for Timescale<SCALE> {
259	fn sub_assign(&mut self, rhs: Self) {
260		*self = *self - rhs;
261	}
262}
263
264// There's no zero Instant, so we need to use a reference point.
265static TIME_ANCHOR: LazyLock<(std::time::Instant, SystemTime)> = LazyLock::new(|| {
266	// To deter nerds trying to use timestamp as wall clock time, we subtract a random amount of time from the anchor.
267	// This will make our timestamps appear to be late; just enough to be annoying and obscure our clock drift.
268	// This will also catch bad implementations that assume unrelated broadcasts are synchronized.
269	let jitter = std::time::Duration::from_millis(rand::rng().random_range(0..69_420));
270	(std::time::Instant::now(), SystemTime::now() - jitter)
271});
272
273// Convert an Instant to a Unix timestamp
274impl<const SCALE: u64> From<std::time::Instant> for Timescale<SCALE> {
275	fn from(instant: std::time::Instant) -> Self {
276		let (anchor_instant, anchor_system) = *TIME_ANCHOR;
277
278		// Conver the instant to a SystemTime.
279		let system = match instant.checked_duration_since(anchor_instant) {
280			Some(forward) => anchor_system + forward,
281			None => anchor_system - anchor_instant.duration_since(instant),
282		};
283
284		// Convert the SystemTime to a Unix timestamp in nanoseconds.
285		// We'll then convert that to the desired scale.
286		system
287			.duration_since(UNIX_EPOCH)
288			.expect("dude your clock is earlier than 1970")
289			.try_into()
290			.expect("dude your clock is later than 2116")
291	}
292}
293
294impl<const SCALE: u64> From<tokio::time::Instant> for Timescale<SCALE> {
295	fn from(instant: tokio::time::Instant) -> Self {
296		instant.into_std().into()
297	}
298}
299
300impl<const SCALE: u64, V> Decode<V> for Timescale<SCALE> {
301	fn decode<R: bytes::Buf>(r: &mut R, version: V) -> Result<Self, DecodeError> {
302		let v = VarInt::decode(r, version)?;
303		Ok(Self(v))
304	}
305}
306
307impl<const SCALE: u64, V> Encode<V> for Timescale<SCALE> {
308	fn encode<W: bytes::BufMut>(&self, w: &mut W, version: V) -> Result<(), EncodeError> {
309		self.0.encode(w, version)?;
310		Ok(())
311	}
312}
313
314#[cfg(test)]
315mod tests {
316	use super::*;
317
318	#[test]
319	fn test_from_secs() {
320		let time = Time::from_secs(5).unwrap();
321		assert_eq!(time.as_secs(), 5);
322		assert_eq!(time.as_millis(), 5000);
323		assert_eq!(time.as_micros(), 5_000_000);
324		assert_eq!(time.as_nanos(), 5_000_000_000);
325	}
326
327	#[test]
328	fn test_from_millis() {
329		let time = Time::from_millis(5000).unwrap();
330		assert_eq!(time.as_secs(), 5);
331		assert_eq!(time.as_millis(), 5000);
332	}
333
334	#[test]
335	fn test_from_micros() {
336		let time = Time::from_micros(5_000_000).unwrap();
337		assert_eq!(time.as_secs(), 5);
338		assert_eq!(time.as_millis(), 5000);
339		assert_eq!(time.as_micros(), 5_000_000);
340	}
341
342	#[test]
343	fn test_from_nanos() {
344		let time = Time::from_nanos(5_000_000_000).unwrap();
345		assert_eq!(time.as_secs(), 5);
346		assert_eq!(time.as_millis(), 5000);
347		assert_eq!(time.as_micros(), 5_000_000);
348		assert_eq!(time.as_nanos(), 5_000_000_000);
349	}
350
351	#[test]
352	fn test_zero() {
353		let time = Time::ZERO;
354		assert_eq!(time.as_secs(), 0);
355		assert_eq!(time.as_millis(), 0);
356		assert_eq!(time.as_micros(), 0);
357		assert_eq!(time.as_nanos(), 0);
358		assert!(time.is_zero());
359	}
360
361	#[test]
362	fn test_roundtrip_millis() {
363		let values = [0, 1, 100, 1000, 999999, 1_000_000_000];
364		for &val in &values {
365			let time = Time::from_millis(val).unwrap();
366			assert_eq!(time.as_millis(), val as u128);
367		}
368	}
369
370	#[test]
371	fn test_roundtrip_micros() {
372		// Note: values < 1000 will lose precision when converting to milliseconds (SCALE=1000)
373		let values = [0, 1000, 1_000_000, 1_000_000_000];
374		for &val in &values {
375			let time = Time::from_micros(val).unwrap();
376			assert_eq!(time.as_micros(), val as u128);
377		}
378	}
379
380	#[test]
381	fn test_different_scale_seconds() {
382		type TimeInSeconds = Timescale<1>;
383		let time = TimeInSeconds::from_secs(5).unwrap();
384		assert_eq!(time.as_secs(), 5);
385		assert_eq!(time.as_millis(), 5000);
386	}
387
388	#[test]
389	fn test_different_scale_microseconds() {
390		type TimeInMicros = Timescale<1_000_000>;
391		let time = TimeInMicros::from_micros(5_000_000).unwrap();
392		assert_eq!(time.as_secs(), 5);
393		assert_eq!(time.as_micros(), 5_000_000);
394	}
395
396	#[test]
397	fn test_scale_conversion() {
398		// Converting 5000 milliseconds at scale 1000 to scale 1000 (should be identity)
399		let time = Time::from_scale(5000, 1000).unwrap();
400		assert_eq!(time.as_millis(), 5000);
401		assert_eq!(time.as_secs(), 5);
402
403		// Converting 5 seconds at scale 1 to scale 1000
404		let time = Time::from_scale(5, 1).unwrap();
405		assert_eq!(time.as_millis(), 5000);
406		assert_eq!(time.as_secs(), 5);
407	}
408
409	#[test]
410	fn test_add() {
411		let a = Time::from_secs(3).unwrap();
412		let b = Time::from_secs(2).unwrap();
413		let c = a + b;
414		assert_eq!(c.as_secs(), 5);
415		assert_eq!(c.as_millis(), 5000);
416	}
417
418	#[test]
419	fn test_sub() {
420		let a = Time::from_secs(5).unwrap();
421		let b = Time::from_secs(2).unwrap();
422		let c = a - b;
423		assert_eq!(c.as_secs(), 3);
424		assert_eq!(c.as_millis(), 3000);
425	}
426
427	#[test]
428	fn test_checked_add() {
429		let a = Time::from_millis(1000).unwrap();
430		let b = Time::from_millis(2000).unwrap();
431		let c = a.checked_add(b).unwrap();
432		assert_eq!(c.as_millis(), 3000);
433	}
434
435	#[test]
436	fn test_checked_sub() {
437		let a = Time::from_millis(5000).unwrap();
438		let b = Time::from_millis(2000).unwrap();
439		let c = a.checked_sub(b).unwrap();
440		assert_eq!(c.as_millis(), 3000);
441	}
442
443	#[test]
444	fn test_checked_sub_underflow() {
445		let a = Time::from_millis(1000).unwrap();
446		let b = Time::from_millis(2000).unwrap();
447		assert!(a.checked_sub(b).is_err());
448	}
449
450	#[test]
451	fn test_max() {
452		let a = Time::from_secs(5).unwrap();
453		let b = Time::from_secs(10).unwrap();
454		assert_eq!(a.max(b), b);
455		assert_eq!(b.max(a), b);
456	}
457
458	#[test]
459	fn test_duration_conversion() {
460		let duration = std::time::Duration::from_secs(5);
461		let time: Time = duration.try_into().unwrap();
462		assert_eq!(time.as_secs(), 5);
463		assert_eq!(time.as_millis(), 5000);
464
465		let duration_back: std::time::Duration = time.into();
466		assert_eq!(duration_back.as_secs(), 5);
467	}
468
469	#[test]
470	fn test_duration_with_nanos() {
471		let duration = std::time::Duration::new(5, 500_000_000); // 5.5 seconds
472		let time: Time = duration.try_into().unwrap();
473		assert_eq!(time.as_millis(), 5500);
474
475		let duration_back: std::time::Duration = time.into();
476		assert_eq!(duration_back.as_millis(), 5500);
477	}
478
479	#[test]
480	fn test_fractional_conversion() {
481		// Test that 1500 millis = 1.5 seconds
482		let time = Time::from_millis(1500).unwrap();
483		assert_eq!(time.as_secs(), 1); // Integer division
484		assert_eq!(time.as_millis(), 1500);
485		assert_eq!(time.as_micros(), 1_500_000);
486	}
487
488	#[test]
489	fn test_precision_loss() {
490		// When converting from a finer scale to coarser, we lose precision
491		// 1234 micros = 1.234 millis, which rounds down to 1 millisecond internally
492		// When converting back, we get 1000 micros, not the original 1234
493		let time = Time::from_micros(1234).unwrap();
494		assert_eq!(time.as_millis(), 1); // 1234 micros = 1.234 millis, rounds to 1
495		assert_eq!(time.as_micros(), 1000); // Precision lost: 1 milli = 1000 micros
496	}
497
498	#[test]
499	fn test_scale_boundaries() {
500		// Test values near scale boundaries
501		let time = Time::from_millis(999).unwrap();
502		assert_eq!(time.as_secs(), 0);
503		assert_eq!(time.as_millis(), 999);
504
505		let time = Time::from_millis(1000).unwrap();
506		assert_eq!(time.as_secs(), 1);
507		assert_eq!(time.as_millis(), 1000);
508
509		let time = Time::from_millis(1001).unwrap();
510		assert_eq!(time.as_secs(), 1);
511		assert_eq!(time.as_millis(), 1001);
512	}
513
514	#[test]
515	fn test_large_values() {
516		// Test with large but valid values
517		let large_secs = 1_000_000_000u64; // ~31 years
518		let time = Time::from_secs(large_secs).unwrap();
519		assert_eq!(time.as_secs(), large_secs);
520	}
521
522	#[test]
523	fn test_new() {
524		let time = Time::new(5000); // 5000 in the current scale (millis)
525		assert_eq!(time.as_millis(), 5000);
526		assert_eq!(time.as_secs(), 5);
527	}
528
529	#[test]
530	fn test_new_u64() {
531		let time = Time::new_u64(5000).unwrap();
532		assert_eq!(time.as_millis(), 5000);
533	}
534
535	#[test]
536	fn test_ordering() {
537		let a = Time::from_secs(1).unwrap();
538		let b = Time::from_secs(2).unwrap();
539		assert!(a < b);
540		assert!(b > a);
541		assert_eq!(a, a);
542	}
543
544	#[test]
545	fn test_unchecked_variants() {
546		let time = Time::from_secs_unchecked(5);
547		assert_eq!(time.as_secs(), 5);
548
549		let time = Time::from_millis_unchecked(5000);
550		assert_eq!(time.as_millis(), 5000);
551
552		let time = Time::from_micros_unchecked(5_000_000);
553		assert_eq!(time.as_micros(), 5_000_000);
554
555		let time = Time::from_nanos_unchecked(5_000_000_000);
556		assert_eq!(time.as_nanos(), 5_000_000_000);
557
558		let time = Time::from_scale_unchecked(5000, 1000);
559		assert_eq!(time.as_millis(), 5000);
560	}
561
562	#[test]
563	fn test_as_scale() {
564		let time = Time::from_secs(1).unwrap();
565		// 1 second in scale 1000 = 1000
566		assert_eq!(time.as_scale(1000), 1000);
567		// 1 second in scale 1 = 1
568		assert_eq!(time.as_scale(1), 1);
569		// 1 second in scale 1_000_000 = 1_000_000
570		assert_eq!(time.as_scale(1_000_000), 1_000_000);
571	}
572
573	#[test]
574	fn test_convert_to_finer() {
575		// Convert from milliseconds to microseconds (coarser to finer)
576		type TimeInMillis = Timescale<1_000>;
577		type TimeInMicros = Timescale<1_000_000>;
578
579		let time_millis = TimeInMillis::from_millis(5000).unwrap();
580		let time_micros: TimeInMicros = time_millis.convert().unwrap();
581
582		assert_eq!(time_micros.as_millis(), 5000);
583		assert_eq!(time_micros.as_micros(), 5_000_000);
584	}
585
586	#[test]
587	fn test_convert_to_coarser() {
588		// Convert from milliseconds to seconds (finer to coarser)
589		type TimeInMillis = Timescale<1_000>;
590		type TimeInSeconds = Timescale<1>;
591
592		let time_millis = TimeInMillis::from_millis(5000).unwrap();
593		let time_secs: TimeInSeconds = time_millis.convert().unwrap();
594
595		assert_eq!(time_secs.as_secs(), 5);
596		assert_eq!(time_secs.as_millis(), 5000);
597	}
598
599	#[test]
600	fn test_convert_precision_loss() {
601		// Converting 1234 millis to seconds loses precision
602		type TimeInMillis = Timescale<1_000>;
603		type TimeInSeconds = Timescale<1>;
604
605		let time_millis = TimeInMillis::from_millis(1234).unwrap();
606		let time_secs: TimeInSeconds = time_millis.convert().unwrap();
607
608		// 1234 millis = 1.234 seconds, rounds down to 1 second
609		assert_eq!(time_secs.as_secs(), 1);
610		assert_eq!(time_secs.as_millis(), 1000); // Lost 234 millis
611	}
612
613	#[test]
614	fn test_convert_roundtrip() {
615		// Converting to finer and back should preserve value
616		type TimeInMillis = Timescale<1_000>;
617		type TimeInMicros = Timescale<1_000_000>;
618
619		let original = TimeInMillis::from_millis(5000).unwrap();
620		let as_micros: TimeInMicros = original.convert().unwrap();
621		let back_to_millis: TimeInMillis = as_micros.convert().unwrap();
622
623		assert_eq!(original.as_millis(), back_to_millis.as_millis());
624	}
625
626	#[test]
627	fn test_convert_same_scale() {
628		// Converting to the same scale should be identity
629		type TimeInMillis = Timescale<1_000>;
630
631		let time = TimeInMillis::from_millis(5000).unwrap();
632		let converted: TimeInMillis = time.convert().unwrap();
633
634		assert_eq!(time.as_millis(), converted.as_millis());
635	}
636
637	#[test]
638	fn test_convert_microseconds_to_nanoseconds() {
639		type TimeInMicros = Timescale<1_000_000>;
640		type TimeInNanos = Timescale<1_000_000_000>;
641
642		let time_micros = TimeInMicros::from_micros(5_000_000).unwrap();
643		let time_nanos: TimeInNanos = time_micros.convert().unwrap();
644
645		assert_eq!(time_nanos.as_micros(), 5_000_000);
646		assert_eq!(time_nanos.as_nanos(), 5_000_000_000);
647	}
648
649	#[test]
650	fn test_convert_custom_scales() {
651		// Test with unusual custom scales
652		type TimeScale60 = Timescale<60>; // 60Hz
653		type TimeScale90 = Timescale<90>; // 90Hz
654
655		let time60 = TimeScale60::from_scale(120, 60).unwrap(); // 2 seconds at 60Hz
656		let time90: TimeScale90 = time60.convert().unwrap();
657
658		// Both should represent 2 seconds
659		assert_eq!(time60.as_secs(), 2);
660		assert_eq!(time90.as_secs(), 2);
661	}
662
663	#[test]
664	fn test_debug_format_units() {
665		// Test that Debug chooses appropriate units based on value
666
667		// Milliseconds that are clean seconds
668		let t = Time::from_millis(100000).unwrap();
669		assert_eq!(format!("{:?}", t), "100s");
670
671		let t = Time::from_millis(1000).unwrap();
672		assert_eq!(format!("{:?}", t), "1s");
673
674		// Milliseconds that are clean milliseconds
675		let t = Time::from_millis(100).unwrap();
676		assert_eq!(format!("{:?}", t), "100ms");
677
678		let t = Time::from_millis(5500).unwrap();
679		assert_eq!(format!("{:?}", t), "5500ms");
680
681		// Zero
682		let t = Time::ZERO;
683		assert_eq!(format!("{:?}", t), "0s");
684
685		// Test with microsecond-scale time
686		type TimeMicros = Timescale<1_000_000>;
687		let t = TimeMicros::from_micros(1500).unwrap();
688		assert_eq!(format!("{:?}", t), "1500µs");
689
690		let t = TimeMicros::from_micros(1000).unwrap();
691		assert_eq!(format!("{:?}", t), "1ms");
692	}
693}