Skip to main content

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}