1use std::sync::atomic::{AtomicI64, Ordering};
9use std::sync::{Mutex, MutexGuard, OnceLock};
10
11use chrono::{DateTime, SecondsFormat, Utc};
12
13pub fn now_iso8601() -> String {
17 fixed_now().to_rfc3339_opts(SecondsFormat::Secs, true)
18}
19
20fn fixed_now() -> DateTime<Utc> {
21 let pinned_ts = OVERRIDE_TS.load(Ordering::SeqCst);
22 if pinned_ts != i64::MIN {
23 if let Some(dt) = DateTime::from_timestamp(pinned_ts, 0) {
24 return dt;
25 }
26 }
27 if let Ok(s) = std::env::var("SECUNIT_CAPTURE_FIXED_TIME") {
28 if let Ok(dt) = DateTime::parse_from_rfc3339(&s) {
29 return dt.with_timezone(&Utc);
30 }
31 }
32 Utc::now()
33}
34
35static OVERRIDE_TS: AtomicI64 = AtomicI64::new(i64::MIN);
38
39fn fixed_time_lock() -> &'static Mutex<()> {
40 static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
41 LOCK.get_or_init(|| Mutex::new(()))
42}
43
44pub fn set_fixed_time_for_tests(rfc3339: &str) -> FixedTimeGuard {
49 let dt = DateTime::parse_from_rfc3339(rfc3339)
50 .expect("set_fixed_time_for_tests: invalid RFC-3339")
51 .with_timezone(&Utc);
52 let guard = fixed_time_lock().lock().unwrap_or_else(|p| p.into_inner());
53 OVERRIDE_TS.store(dt.timestamp(), Ordering::SeqCst);
54 FixedTimeGuard { _g: guard }
55}
56
57#[must_use = "drop the guard to release the time pin"]
58pub struct FixedTimeGuard {
59 _g: MutexGuard<'static, ()>,
60}
61
62impl Drop for FixedTimeGuard {
63 fn drop(&mut self) {
64 OVERRIDE_TS.store(i64::MIN, Ordering::SeqCst);
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71
72 #[test]
73 fn fixed_time_is_honored_via_helper() {
74 let _g = set_fixed_time_for_tests("2026-05-01T12:00:00Z");
75 assert_eq!(now_iso8601(), "2026-05-01T12:00:00Z");
76 }
77}