use std::sync::OnceLock;
pub trait TimeProvider: Send + Sync + 'static {
fn now_millis(&self) -> i64;
}
struct ChronoTimeProvider;
impl TimeProvider for ChronoTimeProvider {
#[allow(clippy::disallowed_methods)]
fn now_millis(&self) -> i64 {
chrono::Utc::now().timestamp_millis()
}
}
static TIME_PROVIDER: OnceLock<Box<dyn TimeProvider>> = OnceLock::new();
pub fn set_time_provider(provider: impl TimeProvider) -> Result<(), &'static str> {
TIME_PROVIDER
.set(Box::new(provider))
.map_err(|_| "time provider already set")
}
#[inline]
pub fn now_millis() -> i64 {
TIME_PROVIDER
.get_or_init(|| Box::new(ChronoTimeProvider))
.now_millis()
}
#[inline]
pub fn now_secs() -> i64 {
now_millis() / 1000
}
#[inline]
pub fn now_utc() -> chrono::DateTime<chrono::Utc> {
chrono::DateTime::from_timestamp_millis(now_millis())
.expect("time provider returned out-of-range millisecond timestamp")
}
#[inline]
pub fn from_secs(ts: i64) -> Option<chrono::DateTime<chrono::Utc>> {
chrono::DateTime::from_timestamp(ts, 0)
}
#[inline]
pub fn from_secs_or_now(ts: i64) -> chrono::DateTime<chrono::Utc> {
from_secs(ts).unwrap_or_else(now_utc)
}
#[inline]
pub fn from_millis(ts: i64) -> Option<chrono::DateTime<chrono::Utc>> {
chrono::DateTime::from_timestamp_millis(ts)
}
#[inline]
pub fn from_millis_or_now(ts: i64) -> chrono::DateTime<chrono::Utc> {
from_millis(ts).unwrap_or_else(now_utc)
}
pub trait MonotonicProvider: Send + Sync + 'static {
fn now_nanos(&self) -> u64;
}
#[cfg(not(target_arch = "wasm32"))]
struct StdMonotonicProvider {
epoch: std::time::Instant,
}
#[cfg(not(target_arch = "wasm32"))]
impl StdMonotonicProvider {
#[allow(clippy::disallowed_methods)]
fn new() -> Self {
Self {
epoch: std::time::Instant::now(),
}
}
}
#[cfg(not(target_arch = "wasm32"))]
impl MonotonicProvider for StdMonotonicProvider {
fn now_nanos(&self) -> u64 {
self.epoch.elapsed().as_nanos().min(u64::MAX as u128) as u64
}
}
#[cfg(target_arch = "wasm32")]
struct WallDerivedMonotonicProvider {
last: std::sync::atomic::AtomicU64,
}
#[cfg(target_arch = "wasm32")]
impl WallDerivedMonotonicProvider {
const fn new() -> Self {
Self {
last: std::sync::atomic::AtomicU64::new(0),
}
}
}
#[cfg(target_arch = "wasm32")]
impl MonotonicProvider for WallDerivedMonotonicProvider {
fn now_nanos(&self) -> u64 {
use std::sync::atomic::Ordering;
let raw = (now_millis().max(0) as u64).saturating_mul(1_000_000);
let mut last = self.last.load(Ordering::Relaxed);
loop {
let next = raw.max(last);
match self
.last
.compare_exchange_weak(last, next, Ordering::Relaxed, Ordering::Relaxed)
{
Ok(_) => return next,
Err(observed) => last = observed,
}
}
}
}
static MONOTONIC_PROVIDER: OnceLock<Box<dyn MonotonicProvider>> = OnceLock::new();
pub fn set_monotonic_provider(provider: impl MonotonicProvider) -> Result<(), &'static str> {
MONOTONIC_PROVIDER
.set(Box::new(provider))
.map_err(|_| "monotonic provider already set")
}
#[inline]
fn now_nanos() -> u64 {
MONOTONIC_PROVIDER
.get_or_init(default_monotonic_provider)
.now_nanos()
}
#[cfg(not(target_arch = "wasm32"))]
fn default_monotonic_provider() -> Box<dyn MonotonicProvider> {
Box::new(StdMonotonicProvider::new())
}
#[cfg(target_arch = "wasm32")]
fn default_monotonic_provider() -> Box<dyn MonotonicProvider> {
Box::new(WallDerivedMonotonicProvider::new())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Instant(u64);
impl Instant {
#[inline]
pub fn now() -> Self {
Self(now_nanos())
}
#[inline]
pub fn elapsed(&self) -> std::time::Duration {
let now = now_nanos();
std::time::Duration::from_nanos(now.saturating_sub(self.0))
}
#[inline]
pub fn saturating_duration_since(&self, earlier: Instant) -> std::time::Duration {
std::time::Duration::from_nanos(self.0.saturating_sub(earlier.0))
}
}
impl std::ops::Add<std::time::Duration> for Instant {
type Output = Instant;
fn add(self, rhs: std::time::Duration) -> Self {
let rhs_nanos: u64 = rhs.as_nanos().min(u64::MAX as u128) as u64;
Self(self.0.saturating_add(rhs_nanos))
}
}
impl std::ops::Sub<Instant> for Instant {
type Output = std::time::Duration;
fn sub(self, rhs: Instant) -> std::time::Duration {
self.saturating_duration_since(rhs)
}
}