metrique_timesource/lib.rs
1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4#![deny(missing_docs)]
5#![doc = include_str!("../README.md")]
6#![cfg_attr(docsrs, feature(doc_cfg))]
7
8use std::{
9 cell::RefCell,
10 fmt::Debug,
11 marker::PhantomData,
12 ops::Add,
13 time::{Duration, Instant as StdInstant, SystemTime as StdSystemTime, SystemTimeError},
14};
15
16/// Module containing fake time sources for testing
17///
18/// To enable this module, you must enable the `test-util` feature.
19#[cfg(feature = "test-util")]
20pub mod fakes;
21
22/// Trait for providing custom time sources
23///
24/// Implementors of this trait can be used to provide custom time behavior
25/// for testing or specialized use cases.
26pub trait Time: Send + Sync + Debug {
27 /// Get the current system time
28 fn now(&self) -> StdSystemTime;
29
30 /// Get the current instant
31 fn instant(&self) -> StdInstant;
32}
33
34/// Tokio-specific time source implementations
35///
36/// This module provides integration with tokio's time utilities, including
37/// support for tokio's time pause/advance functionality for testing.
38///
39/// This requires that the `tokio` feature be enabled.
40#[cfg(feature = "tokio")]
41pub mod tokio {
42 use std::time::SystemTime;
43
44 use tokio::time::Instant as TokioInstant;
45
46 use crate::{Time, TimeSource};
47 use std::time::Instant as StdInstant;
48
49 impl TimeSource {
50 /// Create a new TimeSource that uses tokio's time utilities
51 ///
52 /// This allows integration with tokio's time pause/advance functionality
53 /// for testing time-dependent code.
54 ///
55 /// This requires that the `tokio` feature be enabled.
56 ///
57 /// # Arguments
58 ///
59 /// * `starting_timestamp` - The initial system time to use
60 ///
61 /// # Returns
62 ///
63 /// A new TimeSource that uses tokio's time utilities
64 ///
65 /// # Examples
66 ///
67 /// ```
68 /// # #[tokio::main(flavor = "current_thread")]
69 /// # async fn main() {
70 /// use std::time::{Duration, UNIX_EPOCH};
71 /// use metrique_timesource::TimeSource;
72 ///
73 /// tokio::time::pause();
74 /// let ts = TimeSource::tokio(UNIX_EPOCH);
75 /// let start = ts.instant();
76 ///
77 /// tokio::time::advance(Duration::from_secs(5)).await;
78 /// assert_eq!(start.elapsed(), Duration::from_secs(5));
79 /// # }
80 /// ```
81 pub fn tokio(starting_timestamp: SystemTime) -> Self {
82 TimeSource::custom(TokioTime::initialize_at(starting_timestamp))
83 }
84 }
85
86 /// A time source implementation that uses tokio's time utilities
87 ///
88 /// This time source integrates with tokio's time pause/advance functionality,
89 /// making it useful for testing time-dependent code.
90 ///
91 /// This requires that the `tokio` feature be enabled.
92 #[derive(Copy, Clone, Debug)]
93 pub struct TokioTime {
94 start_time: TokioInstant,
95 start_system_time: SystemTime,
96 }
97
98 impl TokioTime {
99 /// Initialize a new TokioTime with the current system time
100 ///
101 /// # Returns
102 ///
103 /// A new TokioTime instance initialized with the current system time
104 ///
105 /// # Examples
106 ///
107 /// ```
108 /// use metrique_timesource::tokio::TokioTime;
109 /// use metrique_timesource::TimeSource;
110 ///
111 /// let time = TokioTime::initialize();
112 /// let ts = TimeSource::custom(time);
113 /// ```
114 pub fn initialize() -> Self {
115 Self::initialize_at(SystemTime::now())
116 }
117
118 /// Initialize a new TokioTime with a specific system time
119 ///
120 /// # Arguments
121 ///
122 /// * `initial_time` - The initial system time to use
123 ///
124 /// # Returns
125 ///
126 /// A new TokioTime instance initialized with the specified system time
127 ///
128 /// # Examples
129 ///
130 /// ```
131 /// # #[tokio::main(flavor = "current_thread")]
132 /// # async fn main() {
133 /// use std::time::{Duration, UNIX_EPOCH};
134 /// use metrique_timesource::tokio::TokioTime;
135 /// use metrique_timesource::TimeSource;
136 ///
137 /// tokio::time::pause();
138 /// let time = TokioTime::initialize_at(UNIX_EPOCH);
139 /// let ts = TimeSource::custom(time);
140 ///
141 /// assert_eq!(ts.system_time(), UNIX_EPOCH);
142 /// # }
143 /// ```
144 pub fn initialize_at(initial_time: SystemTime) -> Self {
145 Self {
146 start_time: TokioInstant::now(),
147 start_system_time: initial_time,
148 }
149 }
150 }
151
152 impl Time for TokioTime {
153 fn now(&self) -> SystemTime {
154 self.start_system_time + self.start_time.elapsed()
155 }
156
157 fn instant(&self) -> StdInstant {
158 TokioInstant::now().into_std()
159 }
160 }
161
162 use std::collections::HashMap;
163 use std::sync::{Mutex, OnceLock};
164
165 type RuntimeTimeSourceMap = std::sync::Arc<Mutex<HashMap<::tokio::runtime::Id, TimeSource>>>;
166
167 fn runtime_time_sources() -> &'static RuntimeTimeSourceMap {
168 static MAP: OnceLock<RuntimeTimeSourceMap> = OnceLock::new();
169 MAP.get_or_init(|| std::sync::Arc::new(Mutex::new(HashMap::new())))
170 }
171
172 /// Guard for a runtime-scoped time source override.
173 ///
174 /// When dropped, it removes the time source override for the runtime.
175 #[must_use = "if unused the runtime time source will be immediately removed"]
176 pub struct RuntimeTimeSourceGuard {
177 runtime_id: ::tokio::runtime::Id,
178 map: RuntimeTimeSourceMap,
179 }
180
181 impl Drop for RuntimeTimeSourceGuard {
182 fn drop(&mut self) {
183 if let Ok(mut map) = self.map.lock() {
184 map.remove(&self.runtime_id);
185 }
186 }
187 }
188
189 /// Set a time source override for the current tokio runtime.
190 ///
191 /// Unlike [`set_time_source`](crate::set_time_source) which is thread-local, this
192 /// override applies to all tasks on the runtime.
193 ///
194 /// # Panics
195 /// - If not called from within a tokio runtime.
196 /// - If a runtime time source is already installed for this runtime.
197 ///
198 /// # Examples
199 /// ```
200 /// # #[tokio::main(flavor = "current_thread")]
201 /// # async fn main() {
202 /// use std::time::{Duration, UNIX_EPOCH};
203 /// use metrique_timesource::{TimeSource, time_source};
204 /// use metrique_timesource::tokio::set_time_source_for_current_runtime;
205 ///
206 /// tokio::time::pause();
207 /// let _guard = set_time_source_for_current_runtime(TimeSource::tokio(UNIX_EPOCH));
208 /// assert_eq!(time_source().system_time(), UNIX_EPOCH);
209 /// # }
210 /// ```
211 #[track_caller]
212 pub fn set_time_source_for_current_runtime(time_source: TimeSource) -> RuntimeTimeSourceGuard {
213 let handle = ::tokio::runtime::Handle::current();
214 set_time_source_for_runtime(&handle, time_source)
215 }
216
217 /// Set a time source override for a specific tokio runtime.
218 ///
219 /// This allows installing a time source on a runtime from outside that runtime's context.
220 ///
221 /// # Panics
222 /// If a runtime time source is already installed for this runtime.
223 #[track_caller]
224 pub fn set_time_source_for_runtime(
225 handle: &::tokio::runtime::Handle,
226 time_source: TimeSource,
227 ) -> RuntimeTimeSourceGuard {
228 let runtime_id = handle.id();
229 let map = runtime_time_sources();
230 let already_installed = {
231 let mut guard = map.lock().unwrap();
232 if let std::collections::hash_map::Entry::Vacant(e) = guard.entry(runtime_id) {
233 e.insert(time_source);
234 false
235 } else {
236 true
237 }
238 };
239 assert!(
240 !already_installed,
241 "A time source was already installed for this runtime."
242 );
243 RuntimeTimeSourceGuard {
244 runtime_id,
245 map: map.clone(),
246 }
247 }
248
249 pub(crate) fn try_get_runtime_time_source() -> Option<TimeSource> {
250 let handle = ::tokio::runtime::Handle::try_current().ok()?;
251 runtime_time_sources()
252 .lock()
253 .unwrap()
254 .get(&handle.id())
255 .cloned()
256 }
257
258 #[cfg(test)]
259 mod test {
260 use std::time::{Duration, UNIX_EPOCH};
261
262 use crate::{SystemTime, TimeSource, get_time_source, set_time_source, tokio::TokioTime};
263
264 #[tokio::test]
265 async fn tokio_time_source() {
266 tokio::time::pause();
267 let ts = TimeSource::custom(TokioTime::initialize_at(UNIX_EPOCH));
268 let start = ts.instant();
269 assert_eq!(ts.system_time(), UNIX_EPOCH);
270 tokio::time::advance(Duration::from_secs(1)).await;
271 assert_eq!(ts.system_time(), UNIX_EPOCH + Duration::from_secs(1));
272 assert_eq!(start.elapsed(), Duration::from_secs(1))
273 }
274
275 #[tokio::test]
276 async fn with_tokio_ts() {
277 struct MyMetric {
278 start: SystemTime,
279 end: Option<SystemTime>,
280 }
281 impl MyMetric {
282 fn init() -> Self {
283 MyMetric {
284 start: get_time_source(None).system_time(),
285 end: None,
286 }
287 }
288
289 fn finish(&mut self) {
290 self.end = Some(get_time_source(None).system_time());
291 }
292 }
293
294 tokio::time::pause();
295 let start_time = UNIX_EPOCH + Duration::from_secs(1234);
296 let _guard = set_time_source(TimeSource::custom(TokioTime::initialize_at(start_time)));
297 let mut metric = MyMetric::init();
298 assert_eq!(metric.start, start_time);
299 tokio::time::advance(Duration::from_secs(5)).await;
300 metric.finish();
301
302 assert_eq!(
303 metric.end.unwrap().duration_since(metric.start).unwrap(),
304 Duration::from_secs(5)
305 );
306 }
307
308 #[tokio::test]
309 async fn runtime_time_source_basic() {
310 use crate::tokio::set_time_source_for_current_runtime;
311 tokio::time::pause();
312 let _guard = set_time_source_for_current_runtime(TimeSource::tokio(UNIX_EPOCH));
313 assert_eq!(get_time_source(None).system_time(), UNIX_EPOCH);
314 tokio::time::advance(Duration::from_secs(3)).await;
315 assert_eq!(
316 get_time_source(None).system_time(),
317 UNIX_EPOCH + Duration::from_secs(3)
318 );
319 }
320
321 #[tokio::test]
322 async fn runtime_time_source_works_across_spawn() {
323 use crate::tokio::set_time_source_for_current_runtime;
324 tokio::time::pause();
325 let _guard = set_time_source_for_current_runtime(TimeSource::tokio(UNIX_EPOCH));
326 let handle = tokio::spawn(async {
327 assert_eq!(get_time_source(None).system_time(), UNIX_EPOCH);
328 });
329 handle.await.unwrap();
330 }
331
332 #[tokio::test]
333 async fn thread_local_takes_priority_over_runtime() {
334 use crate::fakes::StaticTimeSource;
335 use crate::tokio::set_time_source_for_current_runtime;
336 tokio::time::pause();
337 let _guard = set_time_source_for_current_runtime(TimeSource::tokio(UNIX_EPOCH));
338 let thread_local_time = UNIX_EPOCH + Duration::from_secs(9999);
339 let _tl_guard = set_time_source(TimeSource::custom(StaticTimeSource::at_time(
340 thread_local_time,
341 )));
342 assert_eq!(get_time_source(None).system_time(), thread_local_time);
343 }
344
345 #[tokio::test]
346 async fn runtime_time_source_cleanup_on_drop() {
347 use crate::tokio::set_time_source_for_current_runtime;
348 tokio::time::pause();
349 {
350 let _guard = set_time_source_for_current_runtime(TimeSource::tokio(UNIX_EPOCH));
351 assert_eq!(get_time_source(None).system_time(), UNIX_EPOCH);
352 }
353 // After guard is dropped, should fall back to system time
354 assert_ne!(get_time_source(None).system_time(), UNIX_EPOCH);
355 }
356
357 #[tokio::test]
358 #[should_panic(expected = "already installed")]
359 async fn runtime_time_source_panics_on_double_install() {
360 use crate::tokio::set_time_source_for_current_runtime;
361 tokio::time::pause();
362 let _guard1 = set_time_source_for_current_runtime(TimeSource::tokio(UNIX_EPOCH));
363 let _guard2 = set_time_source_for_current_runtime(TimeSource::tokio(UNIX_EPOCH));
364 }
365
366 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
367 async fn runtime_time_source_works_on_multithreaded_runtime() {
368 use crate::fakes::StaticTimeSource;
369 use crate::tokio::set_time_source_for_current_runtime;
370 let _guard = set_time_source_for_current_runtime(TimeSource::custom(
371 StaticTimeSource::at_time(UNIX_EPOCH),
372 ));
373 let mut handles = Vec::new();
374 for _ in 0..10 {
375 handles.push(tokio::spawn(async {
376 assert_eq!(get_time_source(None).system_time(), UNIX_EPOCH);
377 }));
378 }
379 for h in handles {
380 h.await.unwrap();
381 }
382 }
383 }
384}
385
386/// Enum representing different time source options
387///
388/// TimeSource provides a unified interface for accessing time, whether from the system
389/// clock or from a custom time source for testing.
390#[derive(Clone, Default)]
391pub enum TimeSource {
392 /// Use the system time
393 #[default]
394 System,
395 #[cfg(feature = "custom-timesource")]
396 /// Use a custom time source
397 Custom(std::sync::Arc<dyn Time + Send + Sync>),
398}
399
400impl std::fmt::Debug for TimeSource {
401 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
402 match self {
403 Self::System => write!(f, "TimeSource::System"),
404 #[cfg(feature = "custom-timesource")]
405 Self::Custom(_) => write!(f, "TimeSource::Custom(...)"),
406 }
407 }
408}
409
410impl TimeSource {
411 /// Get the current [`SystemTime`] from this time source
412 ///
413 /// # Returns
414 ///
415 /// A wrapped SystemTime that maintains a reference to this time source
416 ///
417 /// # Examples
418 ///
419 /// ```
420 /// use metrique_timesource::TimeSource;
421 ///
422 /// let ts = TimeSource::System;
423 /// let now = ts.system_time();
424 /// ```
425 pub fn system_time(&self) -> SystemTime {
426 match self {
427 Self::System => SystemTime::new(StdSystemTime::now(), self),
428 #[cfg(feature = "custom-timesource")]
429 Self::Custom(ts) => SystemTime::new(ts.now(), self),
430 }
431 }
432
433 /// Get the current instant from this time source
434 ///
435 /// # Returns
436 ///
437 /// A wrapped Instant that maintains a reference to this time source
438 ///
439 /// # Examples
440 ///
441 /// ```
442 /// use metrique_timesource::TimeSource;
443 /// use std::time::Duration;
444 ///
445 /// let ts = TimeSource::System;
446 /// let start = ts.instant();
447 /// // Do some work
448 /// let elapsed = start.elapsed();
449 /// ```
450 pub fn instant(&self) -> Instant {
451 match self {
452 Self::System => Instant::new(StdInstant::now(), self),
453 #[cfg(feature = "custom-timesource")]
454 Self::Custom(ts) => Instant::new(ts.instant(), self),
455 }
456 }
457
458 /// Create a new TimeSource with a custom time implementation
459 ///
460 /// This method is only available when the `custom-timesource` feature is enabled.
461 ///
462 /// # Arguments
463 ///
464 /// * `custom` - An implementation of the `Time` trait
465 ///
466 /// # Returns
467 ///
468 /// A new TimeSource that uses the provided custom time implementation
469 ///
470 /// # Examples
471 ///
472 /// ```
473 /// use metrique_timesource::{TimeSource, fakes::StaticTimeSource};
474 /// use std::time::{SystemTime, UNIX_EPOCH};
475 ///
476 /// let static_time = StaticTimeSource::at_time(UNIX_EPOCH);
477 /// let ts = TimeSource::custom(static_time);
478 /// assert_eq!(ts.system_time(), UNIX_EPOCH);
479 /// ```
480 #[cfg(feature = "custom-timesource")]
481 pub fn custom(custom: impl Time + 'static) -> TimeSource {
482 Self::Custom(std::sync::Arc::new(custom))
483 }
484}
485
486// Thread-local time source override
487thread_local! {
488 static THREAD_LOCAL_TIME_SOURCE: RefCell<Option<TimeSource>> = const { RefCell::new(None) };
489}
490
491/// Guard for thread-local time source override
492#[must_use]
493pub struct ThreadLocalTimeSourceGuard {
494 previous: Option<TimeSource>,
495 // mark this as `!Send`, `!Sync` because it accesses a thread local
496 _marker: PhantomData<*const ()>,
497}
498
499impl Drop for ThreadLocalTimeSourceGuard {
500 fn drop(&mut self) {
501 THREAD_LOCAL_TIME_SOURCE.with(|cell| {
502 *cell.borrow_mut() = self.previous.take();
503 });
504 }
505}
506
507#[cfg(feature = "custom-timesource")]
508/// Set a thread-local time source override and return a guard
509/// When the guard is dropped, the thread-local override will be cleared
510///
511/// # Examples
512/// ```
513/// use metrique_timesource::{TimeSource, fakes::StaticTimeSource, time_source, set_time_source};
514/// use std::time::UNIX_EPOCH;
515///
516/// let ts = TimeSource::custom(StaticTimeSource::at_time(UNIX_EPOCH));
517/// let _guard = set_time_source(ts);
518///
519/// assert_eq!(time_source().system_time(), UNIX_EPOCH);
520/// ```
521pub fn set_time_source(time_source: TimeSource) -> ThreadLocalTimeSourceGuard {
522 let previous = THREAD_LOCAL_TIME_SOURCE.with(|cell| cell.borrow_mut().replace(time_source));
523 ThreadLocalTimeSourceGuard {
524 previous,
525 _marker: PhantomData,
526 }
527}
528
529#[cfg(feature = "custom-timesource")]
530/// Run a closure with a thread-local time source override
531pub fn with_time_source<F, R>(time_source: TimeSource, f: F) -> R
532where
533 F: FnOnce() -> R,
534{
535 let _guard = set_time_source(time_source);
536 f()
537}
538
539/// Get the current time source, following the priority order:
540/// 1. Explicitly provided time source
541/// 2. Thread-local override
542/// 3. Tokio task-local override (if `tokio` feature is enabled)
543/// 4. System default
544#[inline]
545pub fn get_time_source(ts: Option<TimeSource>) -> TimeSource {
546 // 1. Explicitly provided time source
547 if let Some(ts) = ts {
548 return ts;
549 }
550
551 #[cfg(feature = "custom-timesource")]
552 {
553 // 2. Thread-local override
554 let thread_local = THREAD_LOCAL_TIME_SOURCE.with(|cell| cell.borrow().clone());
555 if let Some(ts) = thread_local {
556 return ts;
557 }
558 }
559
560 // 3. Tokio runtime-wide override
561 #[cfg(feature = "tokio")]
562 {
563 if let Some(ts) = tokio::try_get_runtime_time_source() {
564 return ts;
565 }
566 }
567
568 // 4. System default
569 TimeSource::System
570}
571
572/// Get the current time source
573///
574/// This is a convenience function that calls `get_time_source(None)`.
575///
576/// # Returns
577///
578/// The current time source, which will be either the thread-local override
579/// if one is set, or the system default.
580///
581/// # Examples
582///
583/// ```
584/// use metrique_timesource::time_source;
585///
586/// let ts = time_source();
587/// let now = ts.system_time();
588/// ```
589#[inline]
590pub fn time_source() -> TimeSource {
591 get_time_source(None)
592}
593
594/// `Instant` wrapper
595///
596/// This may be freely converted into `std::time::Instant` with `.into()`. However,
597/// this will cause `elapsed()` to no longer return correct results if a custom time source is used.
598///
599/// When `custom-timesource` is not enabled, this is exactly the same size as `Instant`. When `custom-timesource` _is_ enabled, it retains a pointer
600/// to the timesource it came from to allow `elapsed()` to work properly.
601#[derive(Clone)]
602#[cfg_attr(not(feature = "custom-timesource"), derive(Copy), repr(transparent))]
603pub struct Instant {
604 value: StdInstant,
605 #[cfg(feature = "custom-timesource")]
606 time_source: TimeSource,
607}
608
609impl From<Instant> for StdInstant {
610 fn from(instant: Instant) -> std::time::Instant {
611 instant.as_std()
612 }
613}
614
615impl std::fmt::Debug for Instant {
616 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
617 self.value.fmt(f)
618 }
619}
620
621impl Instant {
622 /// Create a new Instant from the given TimeSource
623 ///
624 /// # Arguments
625 ///
626 /// * `ts` - The TimeSource to use
627 ///
628 /// # Returns
629 ///
630 /// A new Instant representing the current time from the given TimeSource
631 ///
632 /// # Examples
633 ///
634 /// ```
635 /// use metrique_timesource::{Instant, TimeSource};
636 ///
637 /// let ts = TimeSource::System;
638 /// let now = Instant::now(&ts);
639 /// ```
640 pub fn now(ts: &TimeSource) -> Self {
641 ts.instant()
642 }
643
644 /// Returns the amount of time elapsed since this instant was created
645 ///
646 /// # Returns
647 ///
648 /// The elapsed time as a Duration
649 ///
650 /// # Examples
651 ///
652 /// ```
653 /// use metrique_timesource::{TimeSource, time_source};
654 /// use std::thread;
655 /// use std::time::Duration;
656 ///
657 /// let ts = time_source();
658 /// let start = ts.instant();
659 /// thread::sleep(Duration::from_millis(10));
660 /// let elapsed = start.elapsed();
661 /// assert!(elapsed.as_millis() >= 10);
662 /// ```
663 pub fn elapsed(&self) -> Duration {
664 #[cfg(not(feature = "custom-timesource"))]
665 let ts = TimeSource::System;
666 #[cfg(feature = "custom-timesource")]
667 let ts = &self.time_source;
668
669 ts.instant().as_std() - self.value
670 }
671
672 /// Convert this Instant to a std::time::Instant
673 ///
674 /// # Returns
675 ///
676 /// A std::time::Instant representing the same point in time
677 ///
678 /// # Note
679 ///
680 /// After conversion, elapsed() will no longer respect custom time sources
681 /// if they were being used.
682 pub fn as_std(&self) -> StdInstant {
683 self.value
684 }
685
686 fn new(std: StdInstant, ts: &TimeSource) -> Self {
687 #[cfg(not(feature = "custom-timesource"))]
688 let _ = ts;
689 Self {
690 value: std,
691 #[cfg(feature = "custom-timesource")]
692 time_source: ts.clone(),
693 }
694 }
695}
696
697/// `SystemTime` wrapper
698///
699/// This may be freely converted into `std::time::SystemTime` with `.into()`. However,
700/// this will cause `elapsed()` to no longer return correct results if a custom time source is used.
701///
702/// When `custom-timesource` is not enabled, this is exactly the same size as `SystemTime`. When `custom-timesource` _is_ enabled, it retains a pointer
703/// to the timesource it came from to allow `elapsed()` to work properly.
704#[derive(Clone)]
705#[cfg_attr(not(feature = "custom-timesource"), derive(Copy), repr(transparent))]
706pub struct SystemTime {
707 value: StdSystemTime,
708 #[cfg(feature = "custom-timesource")]
709 time_source: TimeSource,
710}
711
712impl PartialEq for SystemTime {
713 fn eq(&self, other: &SystemTime) -> bool {
714 self.value.eq(&other.value)
715 }
716}
717
718impl Eq for SystemTime {}
719
720impl PartialOrd for SystemTime {
721 fn partial_cmp(&self, other: &SystemTime) -> Option<std::cmp::Ordering> {
722 Some(self.cmp(other))
723 }
724}
725
726impl Ord for SystemTime {
727 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
728 self.value.cmp(&other.value)
729 }
730}
731
732impl PartialEq<StdSystemTime> for SystemTime {
733 fn eq(&self, other: &StdSystemTime) -> bool {
734 self.value.eq(other)
735 }
736}
737
738impl PartialOrd<StdSystemTime> for SystemTime {
739 fn partial_cmp(&self, other: &StdSystemTime) -> Option<std::cmp::Ordering> {
740 Some(self.value.cmp(other))
741 }
742}
743
744impl Add<Duration> for SystemTime {
745 type Output = Self;
746
747 fn add(mut self, rhs: Duration) -> Self::Output {
748 self.value += rhs;
749 self
750 }
751}
752
753impl Debug for SystemTime {
754 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
755 self.value.fmt(f)
756 }
757}
758
759impl SystemTime {
760 /// See [`std::time::SystemTime::duration_since`]
761 pub fn duration_since(
762 &self,
763 earlier: impl Into<StdSystemTime>,
764 ) -> Result<Duration, SystemTimeError> {
765 self.value.duration_since(earlier.into())
766 }
767
768 /// See [`std::time::SystemTime::elapsed`]
769 pub fn elapsed(&self) -> Result<Duration, SystemTimeError> {
770 let now = self.time_source().system_time();
771 now.duration_since(self.value)
772 }
773
774 /// Convert this SystemTime to a std::time::SystemTime
775 ///
776 /// # Returns
777 ///
778 /// A std::time::SystemTime representing the same point in time
779 ///
780 /// # Note
781 ///
782 /// After conversion, elapsed() will no longer respect custom time sources
783 /// if they were being used.
784 pub fn as_std(&self) -> StdSystemTime {
785 self.value
786 }
787
788 fn time_source(&self) -> &TimeSource {
789 #[cfg(feature = "custom-timesource")]
790 {
791 &self.time_source
792 }
793
794 #[cfg(not(feature = "custom-timesource"))]
795 &TimeSource::System
796 }
797
798 /// Creates this SystemTime from a std::time::SystemTime
799 /// and a provided time source. This is useful for loading
800 /// system times from an external source, that you want
801 /// to interact with using this library's time sources.
802 ///
803 /// # Returns
804 ///
805 /// A SystemTime representing the same point in time,
806 /// managed by the provided time source.
807 ///
808 /// # Example
809 ///
810 /// ```
811 /// use metrique_timesource::{SystemTime, time_source};
812 ///
813 /// let now = std::time::SystemTime::now();
814 /// let system_time = SystemTime::new(now, &time_source());
815 /// ```
816 pub fn new(std: StdSystemTime, ts: &TimeSource) -> Self {
817 #[cfg(not(feature = "custom-timesource"))]
818 let _ = ts;
819 Self {
820 value: std,
821 #[cfg(feature = "custom-timesource")]
822 time_source: ts.clone(),
823 }
824 }
825}
826
827impl From<SystemTime> for StdSystemTime {
828 fn from(val: SystemTime) -> Self {
829 val.value
830 }
831}
832
833#[cfg(test)]
834mod tests {
835
836 use std::time::UNIX_EPOCH;
837
838 use crate::{
839 TimeSource, fakes, get_time_source, set_time_source, time_source, with_time_source,
840 };
841
842 #[test]
843 fn test_default_time_source() {
844 let ts = time_source();
845 match ts {
846 TimeSource::System => {} // Expected
847 _ => panic!("Expected default time source to be System"),
848 }
849 }
850
851 #[test]
852 fn test_explicit_time_source() {
853 let ts = fakes::StaticTimeSource::at_time(UNIX_EPOCH);
854 let ts = TimeSource::custom(ts);
855 let ts = get_time_source(Some(ts));
856 match ts {
857 TimeSource::Custom(_) => {} // Expected
858 _ => panic!("Expected explicit time source to be used"),
859 }
860 }
861
862 #[test]
863 fn test_thread_local_time_source() {
864 let ts = fakes::StaticTimeSource::at_time(UNIX_EPOCH);
865 let ts = TimeSource::custom(ts);
866
867 {
868 let _guard = set_time_source(ts);
869 let ts = get_time_source(None);
870 assert_eq!(ts.system_time(), UNIX_EPOCH);
871 }
872
873 // After guard is dropped, should go back to default
874 let ts = get_time_source(None);
875 match ts {
876 TimeSource::System => {} // Expected
877 _ => panic!("Expected default time source after guard is dropped"),
878 }
879 }
880
881 #[test]
882 fn test_thread_local_time_source_scoped() {
883 let ts = fakes::StaticTimeSource::at_time(UNIX_EPOCH);
884 let thread_local = TimeSource::custom(ts);
885
886 with_time_source(thread_local, || {
887 let ts = get_time_source(None);
888 match ts {
889 TimeSource::Custom(_) => {} // Expected
890 _ => panic!(),
891 }
892 });
893
894 // After scope, should go back to default
895 let ts = get_time_source(None);
896 match ts {
897 TimeSource::System => {} // Expected
898 _ => panic!("Expected default time source after scope"),
899 }
900 }
901}