pub mod backend;
pub mod memory;
pub mod native;
use jiff::{Span, Timestamp};
use std::sync::{Mutex, OnceLock};
pub use backend::{ClockBackend, SystemClockError};
static BACKEND: OnceLock<Mutex<Box<dyn ClockBackend>>> = OnceLock::new();
fn backend() -> &'static Mutex<Box<dyn ClockBackend>> {
BACKEND.get_or_init(|| Mutex::new(default_backend()))
}
fn default_backend() -> Box<dyn ClockBackend> {
if cfg!(target_arch = "wasm32") {
Box::new(memory::MemoryBackend::new(jiff::Timestamp::now()))
} else {
Box::new(native::NativeBackend::new())
}
}
pub fn set_backend(new_backend: Box<dyn ClockBackend>) {
let mut backend_guard = backend().lock().unwrap_or_else(|e| e.into_inner());
*backend_guard = new_backend;
}
pub fn reset_backend() {
set_backend(default_backend());
}
static MOCK_OFFSET: OnceLock<Mutex<Span>> = OnceLock::new();
fn mock_slot() -> &'static Mutex<Span> {
MOCK_OFFSET.get_or_init(|| Mutex::new(Span::new()))
}
fn mock_lock() -> std::sync::MutexGuard<'static, Span> {
mock_slot()
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
pub fn get() -> Timestamp {
let offset = *mock_lock();
system_get() + offset
}
pub fn set_date(date: jiff::civil::Date) {
let offset = *mock_lock();
let current = system_get() + offset;
let tz = jiff::tz::TimeZone::system();
let current_zoned = jiff::Zoned::new(current, tz.clone());
let t = current_zoned.time();
let target = date.at(t.hour(), t.minute(), t.second(), t.subsec_nanosecond());
let target_zoned = target.to_zoned(tz).unwrap();
let target_ts = target_zoned.timestamp();
let now = system_get();
*mock_lock() = target_ts - now;
}
pub fn set_time(time: jiff::civil::Time) {
let offset = *mock_lock();
let current = system_get() + offset;
let tz = jiff::tz::TimeZone::system();
let current_zoned = jiff::Zoned::new(current, tz.clone());
let d = current_zoned.date();
let target = d.at(
time.hour(),
time.minute(),
time.second(),
time.subsec_nanosecond(),
);
let target_zoned = target.to_zoned(tz).unwrap();
let target_ts = target_zoned.timestamp();
let now = system_get();
*mock_lock() = target_ts - now;
}
pub fn reset() {
*mock_lock() = Span::new();
}
pub fn system_get() -> Timestamp {
backend().lock().unwrap_or_else(|e| e.into_inner()).now()
}
pub fn system_set(ts: Timestamp) -> Result<(), SystemClockError> {
backend().lock().unwrap_or_else(|e| e.into_inner()).set(ts)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::state::test_support::TEST_LOCK;
fn diff_from_now(ts: Timestamp) -> std::time::Duration {
let real = jiff::Timestamp::now();
let span = ts - real;
let sd: jiff::SignedDuration = span.try_into().unwrap();
sd.unsigned_abs()
}
#[test]
fn default_gets_real_time() {
let _guard = TEST_LOCK.lock().unwrap();
reset();
let ts = get();
assert!(diff_from_now(ts) < std::time::Duration::from_secs(1));
}
#[test]
fn set_date_shifts_clock() {
let _guard = TEST_LOCK.lock().unwrap();
reset();
let target = jiff::civil::Date::new(2025, 6, 15).unwrap();
set_date(target);
let ts = get();
let zoned = jiff::Zoned::new(ts, jiff::tz::TimeZone::system());
assert_eq!(zoned.date(), target);
reset();
}
#[test]
fn set_time_shifts_clock() {
let _guard = TEST_LOCK.lock().unwrap();
reset();
let target = jiff::civil::Time::new(14, 30, 0, 0).unwrap();
set_time(target);
let ts = get();
let zoned = jiff::Zoned::new(ts, jiff::tz::TimeZone::system());
let t = zoned.time();
assert_eq!(t.hour(), target.hour());
assert_eq!(t.minute(), target.minute());
assert_eq!(t.second(), target.second());
reset();
}
#[test]
fn reset_clears_offset() {
let _guard = TEST_LOCK.lock().unwrap();
let target = jiff::civil::Date::new(2025, 6, 15).unwrap();
set_date(target);
reset();
assert!(diff_from_now(get()) < std::time::Duration::from_secs(1));
}
#[test]
fn system_get_returns_real_time() {
let ts = system_get();
assert!(diff_from_now(ts) < std::time::Duration::from_secs(1));
}
#[test]
fn system_set_rejected_without_privileges() {
let _ = system_set(jiff::Timestamp::now());
}
}