#![deny(missing_docs)]
#![doc = include_str!("../README.md")]
use std::{
cell::RefCell,
fmt::Debug,
ops::Add,
time::{Duration, Instant as StdInstant, SystemTime as StdSystemTime, SystemTimeError},
};
#[cfg(feature = "test-util")]
pub mod fakes;
pub trait Time: Send + Sync + Debug {
fn now(&self) -> StdSystemTime;
fn instant(&self) -> StdInstant;
}
#[cfg(feature = "tokio")]
pub mod tokio {
use std::time::SystemTime;
use tokio::time::Instant as TokioInstant;
use crate::{Time, TimeSource};
use std::time::Instant as StdInstant;
impl TimeSource {
pub fn tokio(starting_timestamp: SystemTime) -> Self {
TimeSource::custom(TokioTime::initialize_at(starting_timestamp))
}
}
#[derive(Copy, Clone, Debug)]
pub struct TokioTime {
start_time: TokioInstant,
start_system_time: SystemTime,
}
impl TokioTime {
pub fn initialize() -> Self {
Self::initialize_at(SystemTime::now())
}
pub fn initialize_at(initial_time: SystemTime) -> Self {
Self {
start_time: TokioInstant::now(),
start_system_time: initial_time,
}
}
}
impl Time for TokioTime {
fn now(&self) -> SystemTime {
self.start_system_time + self.start_time.elapsed()
}
fn instant(&self) -> StdInstant {
TokioInstant::now().into_std()
}
}
#[cfg(test)]
mod test {
use std::time::{Duration, UNIX_EPOCH};
use crate::{SystemTime, TimeSource, get_time_source, set_time_source, tokio::TokioTime};
#[tokio::test]
async fn tokio_time_source() {
tokio::time::pause();
let ts = TimeSource::custom(TokioTime::initialize_at(UNIX_EPOCH));
let start = ts.instant();
assert_eq!(ts.system_time(), UNIX_EPOCH);
tokio::time::advance(Duration::from_secs(1)).await;
assert_eq!(ts.system_time(), UNIX_EPOCH + Duration::from_secs(1));
assert_eq!(start.elapsed(), Duration::from_secs(1))
}
#[tokio::test]
async fn with_tokio_ts() {
struct MyMetric {
start: SystemTime,
end: Option<SystemTime>,
}
impl MyMetric {
fn init() -> Self {
MyMetric {
start: get_time_source(None).system_time(),
end: None,
}
}
fn finish(&mut self) {
self.end = Some(get_time_source(None).system_time());
}
}
tokio::time::pause();
let start_time = UNIX_EPOCH + Duration::from_secs(1234);
let _guard = set_time_source(TimeSource::custom(TokioTime::initialize_at(start_time)));
let mut metric = MyMetric::init();
assert_eq!(metric.start, start_time);
tokio::time::advance(Duration::from_secs(5)).await;
metric.finish();
assert_eq!(
metric.end.unwrap().duration_since(metric.start).unwrap(),
Duration::from_secs(5)
);
}
}
}
#[derive(Clone)]
pub enum TimeSource {
System,
#[cfg(feature = "custom-timesource")]
Custom(std::sync::Arc<dyn Time + Send + Sync>),
}
impl std::fmt::Debug for TimeSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::System => write!(f, "TimeSource::System"),
#[cfg(feature = "custom-timesource")]
Self::Custom(_) => write!(f, "TimeSource::Custom(...)"),
}
}
}
impl TimeSource {
pub fn system_time(&self) -> SystemTime {
match self {
Self::System => SystemTime::new(StdSystemTime::now(), self),
#[cfg(feature = "custom-timesource")]
Self::Custom(ts) => SystemTime::new(ts.now(), self),
}
}
pub fn instant(&self) -> Instant {
match self {
Self::System => Instant::new(StdInstant::now(), self),
#[cfg(feature = "custom-timesource")]
Self::Custom(ts) => Instant::new(ts.instant(), self),
}
}
#[cfg(feature = "custom-timesource")]
pub fn custom(custom: impl Time + 'static) -> TimeSource {
Self::Custom(std::sync::Arc::new(custom))
}
}
impl Default for TimeSource {
fn default() -> Self {
Self::System
}
}
thread_local! {
static THREAD_LOCAL_TIME_SOURCE: RefCell<Option<TimeSource>> = const { RefCell::new(None) };
}
#[must_use]
pub struct ThreadLocalTimeSourceGuard {
previous: Option<TimeSource>,
}
impl Drop for ThreadLocalTimeSourceGuard {
fn drop(&mut self) {
THREAD_LOCAL_TIME_SOURCE.with(|cell| {
*cell.borrow_mut() = self.previous.take();
});
}
}
#[cfg(feature = "custom-timesource")]
pub fn set_time_source(time_source: TimeSource) -> ThreadLocalTimeSourceGuard {
let previous = THREAD_LOCAL_TIME_SOURCE.with(|cell| cell.borrow_mut().replace(time_source));
ThreadLocalTimeSourceGuard { previous }
}
#[cfg(feature = "custom-timesource")]
pub fn with_time_source<F, R>(time_source: TimeSource, f: F) -> R
where
F: FnOnce() -> R,
{
let _guard = set_time_source(time_source);
f()
}
#[inline]
pub fn get_time_source(ts: Option<TimeSource>) -> TimeSource {
if let Some(ts) = ts {
return ts;
}
#[cfg(feature = "custom-timesource")]
{
let thread_local = THREAD_LOCAL_TIME_SOURCE.with(|cell| cell.borrow().clone());
if let Some(ts) = thread_local {
return ts;
}
}
TimeSource::System
}
#[inline]
pub fn time_source() -> TimeSource {
get_time_source(None)
}
#[derive(Clone)]
#[cfg_attr(not(feature = "custom-timesource"), derive(Copy), repr(transparent))]
pub struct Instant {
value: StdInstant,
#[cfg(feature = "custom-timesource")]
time_source: TimeSource,
}
impl From<Instant> for StdInstant {
fn from(instant: Instant) -> std::time::Instant {
instant.as_std()
}
}
impl std::fmt::Debug for Instant {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.value.fmt(f)
}
}
impl Instant {
pub fn now(ts: &TimeSource) -> Self {
ts.instant()
}
pub fn elapsed(&self) -> Duration {
#[cfg(not(feature = "custom-timesource"))]
let ts = TimeSource::System;
#[cfg(feature = "custom-timesource")]
let ts = &self.time_source;
ts.instant().as_std() - self.value
}
pub fn as_std(&self) -> StdInstant {
self.value
}
fn new(std: StdInstant, ts: &TimeSource) -> Self {
#[cfg(not(feature = "custom-timesource"))]
let _ = ts;
Self {
value: std,
#[cfg(feature = "custom-timesource")]
time_source: ts.clone(),
}
}
}
#[derive(Clone)]
#[cfg_attr(not(feature = "custom-timesource"), derive(Copy), repr(transparent))]
pub struct SystemTime {
value: StdSystemTime,
#[cfg(feature = "custom-timesource")]
time_source: TimeSource,
}
impl PartialEq for SystemTime {
fn eq(&self, other: &SystemTime) -> bool {
self.value.eq(&other.value)
}
}
impl Eq for SystemTime {}
impl PartialOrd for SystemTime {
fn partial_cmp(&self, other: &SystemTime) -> Option<std::cmp::Ordering> {
Some(self.value.cmp(&other.value))
}
}
impl Ord for SystemTime {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.value.cmp(&other.value)
}
}
impl PartialEq<StdSystemTime> for SystemTime {
fn eq(&self, other: &StdSystemTime) -> bool {
self.value.eq(other)
}
}
impl PartialOrd<StdSystemTime> for SystemTime {
fn partial_cmp(&self, other: &StdSystemTime) -> Option<std::cmp::Ordering> {
Some(self.value.cmp(other))
}
}
impl Add<Duration> for SystemTime {
type Output = Self;
fn add(mut self, rhs: Duration) -> Self::Output {
self.value += rhs;
self
}
}
impl Debug for SystemTime {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.value.fmt(f)
}
}
impl SystemTime {
pub fn duration_since(
&self,
earlier: impl Into<StdSystemTime>,
) -> Result<Duration, SystemTimeError> {
self.value.duration_since(earlier.into())
}
pub fn elapsed(&self) -> Result<Duration, SystemTimeError> {
let now = self.time_source().system_time();
now.duration_since(self.value)
}
pub fn as_std(&self) -> StdSystemTime {
self.value
}
fn time_source(&self) -> &TimeSource {
#[cfg(feature = "custom-timesource")]
{
&self.time_source
}
#[cfg(not(feature = "custom-timesource"))]
&TimeSource::System
}
pub fn new(std: StdSystemTime, ts: &TimeSource) -> Self {
#[cfg(not(feature = "custom-timesource"))]
let _ = ts;
Self {
value: std,
#[cfg(feature = "custom-timesource")]
time_source: ts.clone(),
}
}
}
impl From<SystemTime> for StdSystemTime {
fn from(val: SystemTime) -> Self {
val.value
}
}
#[cfg(test)]
mod tests {
use std::time::UNIX_EPOCH;
use crate::{
TimeSource, fakes, get_time_source, set_time_source, time_source, with_time_source,
};
#[test]
fn test_default_time_source() {
let ts = time_source();
match ts {
TimeSource::System => {} _ => panic!("Expected default time source to be System"),
}
}
#[test]
fn test_explicit_time_source() {
let ts = fakes::StaticTimeSource::at_time(UNIX_EPOCH);
let ts = TimeSource::custom(ts);
let ts = get_time_source(Some(ts));
match ts {
TimeSource::Custom(_) => {} _ => panic!("Expected explicit time source to be used"),
}
}
#[test]
fn test_thread_local_time_source() {
let ts = fakes::StaticTimeSource::at_time(UNIX_EPOCH);
let ts = TimeSource::custom(ts);
{
let _guard = set_time_source(ts);
let ts = get_time_source(None);
assert_eq!(ts.system_time(), UNIX_EPOCH);
}
let ts = get_time_source(None);
match ts {
TimeSource::System => {} _ => panic!("Expected default time source after guard is dropped"),
}
}
#[test]
fn test_thread_local_time_source_scoped() {
let ts = fakes::StaticTimeSource::at_time(UNIX_EPOCH);
let thread_local = TimeSource::custom(ts);
with_time_source(thread_local, || {
let ts = get_time_source(None);
match ts {
TimeSource::Custom(_) => {} _ => panic!(),
}
});
let ts = get_time_source(None);
match ts {
TimeSource::System => {} _ => panic!("Expected default time source after scope"),
}
}
}