use crate::uuid_factory::{DeterministicUuidFactory, GeneratorType};
use chrono::{DateTime, NaiveDate, Utc};
use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering};
use uuid::Uuid;
const UNSET: i64 = i64::MIN;
static DET_EPOCH_MILLIS: AtomicI64 = AtomicI64::new(UNSET);
const DET_UUID_SUB: u8 = 0xFF;
static DET_UUID_ACTIVE: AtomicBool = AtomicBool::new(false);
static DET_UUID_SEED: AtomicU64 = AtomicU64::new(0);
static DET_UUID_COUNTER: AtomicU64 = AtomicU64::new(0);
pub fn set_deterministic_epoch(epoch: Option<DateTime<Utc>>) {
let v = epoch.map(|e| e.timestamp_millis()).unwrap_or(UNSET);
DET_EPOCH_MILLIS.store(v, Ordering::SeqCst);
}
pub fn is_deterministic() -> bool {
DET_EPOCH_MILLIS.load(Ordering::SeqCst) != UNSET
}
pub fn now() -> DateTime<Utc> {
let v = DET_EPOCH_MILLIS.load(Ordering::SeqCst);
if v == UNSET {
Utc::now()
} else {
DateTime::from_timestamp_millis(v).unwrap_or_else(Utc::now)
}
}
pub fn set_deterministic_uuids(seed: Option<u64>) {
match seed {
Some(s) => {
DET_UUID_SEED.store(s, Ordering::SeqCst);
DET_UUID_COUNTER.store(0, Ordering::SeqCst);
DET_UUID_ACTIVE.store(true, Ordering::SeqCst);
}
None => DET_UUID_ACTIVE.store(false, Ordering::SeqCst),
}
}
pub fn next_document_id() -> Uuid {
if DET_UUID_ACTIVE.load(Ordering::SeqCst) {
let n = DET_UUID_COUNTER.fetch_add(1, Ordering::SeqCst);
let seed = DET_UUID_SEED.load(Ordering::SeqCst);
DeterministicUuidFactory::with_sub_discriminator(
seed,
GeneratorType::JournalEntry,
DET_UUID_SUB,
)
.generate_at(n)
} else {
Uuid::now_v7()
}
}
pub fn epoch_from_seed(seed: u64, start_date: NaiveDate) -> DateTime<Utc> {
let base = start_date
.and_hms_opt(0, 0, 0)
.unwrap_or_default()
.and_utc();
let offset_secs = (seed % 86_400) as i64;
base + chrono::Duration::seconds(offset_secs)
}
#[must_use = "the deterministic clock is only active while the guard is alive"]
pub struct DeterministicClockGuard {
_private: (),
}
impl DeterministicClockGuard {
pub fn new(epoch: DateTime<Utc>) -> Self {
set_deterministic_epoch(Some(epoch));
Self { _private: () }
}
pub fn from_seed(seed: u64, start_date: NaiveDate) -> Self {
set_deterministic_epoch(Some(epoch_from_seed(seed, start_date)));
set_deterministic_uuids(Some(seed));
Self { _private: () }
}
}
impl Drop for DeterministicClockGuard {
fn drop(&mut self) {
set_deterministic_epoch(None);
set_deterministic_uuids(None);
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
static CLOCK_CTX_LOCK: Mutex<()> = Mutex::new(());
#[test]
fn test_epoch_from_seed_is_deterministic() {
let d = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
assert_eq!(epoch_from_seed(42, d), epoch_from_seed(42, d));
assert_ne!(epoch_from_seed(42, d), epoch_from_seed(43, d));
assert!(epoch_from_seed(0, d) >= d.and_hms_opt(0, 0, 0).unwrap().and_utc());
}
#[test]
fn test_next_document_id_deterministic_under_context() {
let _ctx = CLOCK_CTX_LOCK.lock().unwrap_or_else(|e| e.into_inner());
set_deterministic_uuids(Some(99));
let s1: Vec<Uuid> = (0..3).map(|_| next_document_id()).collect();
set_deterministic_uuids(Some(99));
let s2: Vec<Uuid> = (0..3).map(|_| next_document_id()).collect();
set_deterministic_uuids(None);
assert_eq!(s1, s2, "same seed → identical id sequence");
assert_ne!(s1[0], s1[1], "sequential ids are distinct");
assert!(!s1[0].is_nil());
}
#[test]
fn test_now_honors_epoch_and_guard_restores() {
let _ctx = CLOCK_CTX_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let epoch = NaiveDate::from_ymd_opt(2024, 6, 7)
.unwrap()
.and_hms_opt(12, 0, 0)
.unwrap()
.and_utc();
assert!(!is_deterministic(), "must start unset");
{
let _g = DeterministicClockGuard::new(epoch);
assert!(is_deterministic());
assert_eq!(now(), epoch);
assert_eq!(now(), epoch, "stable across calls");
}
assert!(!is_deterministic());
assert_ne!(now(), epoch);
}
}