reifydb_core/util/
clock.rs

1// Copyright (c) reifydb.com 2025
2// This file is licensed under the AGPL-3.0-or-later, see license.md file
3
4#[cfg(debug_assertions)]
5use std::cell::RefCell;
6use std::time::{SystemTime, UNIX_EPOCH};
7
8use reifydb_type::value::DateTime;
9
10#[cfg(debug_assertions)]
11thread_local! {
12    // Store as nanoseconds internally for maximum precision
13    static MOCK_TIME_NANOS: RefCell<Option<u128>> = RefCell::new(None);
14}
15
16/// Get current time in nanoseconds since Unix epoch
17/// In release builds, this is a direct system call
18/// In debug builds, this checks for mock time override
19#[inline(always)]
20pub fn now_nanos() -> u128 {
21	#[cfg(debug_assertions)]
22	{
23		if let Some(nanos) = MOCK_TIME_NANOS.with(|c| *c.borrow()) {
24			return nanos;
25		}
26	}
27
28	SystemTime::now().duration_since(UNIX_EPOCH).expect("System time is before Unix epoch").as_nanos()
29}
30
31/// Get current time in microseconds since Unix epoch
32#[inline(always)]
33pub fn now_micros() -> u64 {
34	(now_nanos() / 1_000) as u64
35}
36
37/// Get current time in milliseconds since Unix epoch
38#[inline(always)]
39pub fn now_millis() -> u64 {
40	(now_nanos() / 1_000_000) as u64
41}
42
43/// Get current time as DateTime
44/// Preserves nanosecond precision when available
45#[inline(always)]
46pub fn now() -> DateTime {
47	let nanos = now_nanos();
48	let secs = (nanos / 1_000_000_000) as i64;
49	let nanos_remainder = (nanos % 1_000_000_000) as u32;
50
51	DateTime::from_parts(secs, nanos_remainder).unwrap_or_else(|_| DateTime::now())
52}
53
54// ============================================================================
55// Mock Time Functions (only available in debug builds)
56// ============================================================================
57
58/// Set mock time to a specific value in nanoseconds
59#[cfg(debug_assertions)]
60pub fn mock_time_set_nanos(nanos: u128) {
61	MOCK_TIME_NANOS.with(|c| {
62		*c.borrow_mut() = Some(nanos);
63	});
64}
65
66/// Set mock time to a specific value in microseconds
67#[cfg(debug_assertions)]
68pub fn mock_time_set_micros(micros: u64) {
69	mock_time_set_nanos(micros as u128 * 1_000);
70}
71
72/// Set mock time to a specific value in milliseconds
73#[cfg(debug_assertions)]
74pub fn mock_time_set_millis(millis: u64) {
75	mock_time_set_nanos(millis as u128 * 1_000_000);
76}
77
78/// Set mock time to a specific value in milliseconds (convenience alias)
79#[cfg(debug_assertions)]
80pub fn mock_time_set(millis: u64) {
81	mock_time_set_millis(millis);
82}
83
84/// Advance mock time by specified nanoseconds
85#[cfg(debug_assertions)]
86pub fn mock_time_advance_nanos(nanos: u128) {
87	MOCK_TIME_NANOS.with(|c| {
88		let mut time = c.borrow_mut();
89		let current = time.unwrap_or_else(|| now_nanos());
90		*time = Some(current + nanos);
91	});
92}
93
94/// Advance mock time by specified microseconds
95#[cfg(debug_assertions)]
96pub fn mock_time_advance_micros(micros: u64) {
97	mock_time_advance_nanos(micros as u128 * 1_000);
98}
99
100/// Advance mock time by specified milliseconds
101#[cfg(debug_assertions)]
102pub fn mock_time_advance_millis(millis: u64) {
103	mock_time_advance_nanos(millis as u128 * 1_000_000);
104}
105
106/// Advance mock time by specified milliseconds (convenience alias)
107#[cfg(debug_assertions)]
108pub fn mock_time_advance(millis: u64) {
109	mock_time_advance_millis(millis);
110}
111
112/// Clear mock time override
113#[cfg(debug_assertions)]
114pub fn mock_time_clear() {
115	MOCK_TIME_NANOS.with(|c| {
116		*c.borrow_mut() = None;
117	});
118}
119
120/// Get current mock time value in nanoseconds
121#[cfg(debug_assertions)]
122pub fn mock_time_get_nanos() -> Option<u128> {
123	MOCK_TIME_NANOS.with(|c| *c.borrow())
124}
125
126/// Get current mock time value in microseconds
127#[cfg(debug_assertions)]
128pub fn mock_time_get_micros() -> Option<u64> {
129	mock_time_get_nanos().map(|n| (n / 1_000) as u64)
130}
131
132/// Get current mock time value in milliseconds
133#[cfg(debug_assertions)]
134pub fn mock_time_get_millis() -> Option<u64> {
135	mock_time_get_nanos().map(|n| (n / 1_000_000) as u64)
136}
137
138/// Get current mock time value in milliseconds (convenience alias)
139#[cfg(debug_assertions)]
140pub fn mock_time_get() -> Option<u64> {
141	mock_time_get_millis()
142}
143
144/// Check if mock time is currently active
145#[cfg(debug_assertions)]
146pub fn mock_time_is_active() -> bool {
147	MOCK_TIME_NANOS.with(|c| c.borrow().is_some())
148}
149
150/// RAII guard for scoped mock time
151#[cfg(debug_assertions)]
152pub struct MockTimeGuard {
153	prev: Option<u128>,
154}
155
156#[cfg(debug_assertions)]
157impl Drop for MockTimeGuard {
158	fn drop(&mut self) {
159		MOCK_TIME_NANOS.with(|c| {
160			*c.borrow_mut() = self.prev;
161		});
162	}
163}
164
165/// Set mock time with RAII guard that restores previous value
166#[cfg(debug_assertions)]
167pub fn mock_time_scoped_nanos(nanos: u128) -> MockTimeGuard {
168	MOCK_TIME_NANOS.with(|c| {
169		let prev = *c.borrow();
170		*c.borrow_mut() = Some(nanos);
171		MockTimeGuard {
172			prev,
173		}
174	})
175}
176
177/// Set mock time in microseconds with RAII guard
178#[cfg(debug_assertions)]
179pub fn mock_time_scoped_micros(micros: u64) -> MockTimeGuard {
180	mock_time_scoped_nanos(micros as u128 * 1_000)
181}
182
183/// Set mock time in milliseconds with RAII guard
184#[cfg(debug_assertions)]
185pub fn mock_time_scoped_millis(millis: u64) -> MockTimeGuard {
186	mock_time_scoped_nanos(millis as u128 * 1_000_000)
187}
188
189/// Set mock time in milliseconds with RAII guard (convenience alias)
190#[cfg(debug_assertions)]
191pub fn mock_time_scoped(millis: u64) -> MockTimeGuard {
192	mock_time_scoped_millis(millis)
193}
194
195/// Run a function with mock time in nanoseconds
196#[cfg(debug_assertions)]
197pub fn mock_time_with_nanos<T>(nanos: u128, f: impl FnOnce() -> T) -> T {
198	let _guard = mock_time_scoped_nanos(nanos);
199	f()
200}
201
202/// Run a function with mock time in microseconds
203#[cfg(debug_assertions)]
204pub fn mock_time_with_micros<T>(micros: u64, f: impl FnOnce() -> T) -> T {
205	let _guard = mock_time_scoped_micros(micros);
206	f()
207}
208
209/// Run a function with mock time in milliseconds
210#[cfg(debug_assertions)]
211pub fn mock_time_with_millis<T>(millis: u64, f: impl FnOnce() -> T) -> T {
212	let _guard = mock_time_scoped_millis(millis);
213	f()
214}
215
216/// Run a function with mock time in milliseconds (convenience alias)
217#[cfg(debug_assertions)]
218pub fn mock_time_with<T>(millis: u64, f: impl FnOnce() -> T) -> T {
219	mock_time_with_millis(millis, f)
220}
221
222/// Control handle for advancing mock time within a scope
223#[cfg(debug_assertions)]
224pub struct MockTimeControl;
225
226#[cfg(debug_assertions)]
227impl MockTimeControl {
228	pub fn advance_nanos(&self, nanos: u128) {
229		mock_time_advance_nanos(nanos);
230	}
231
232	pub fn advance_micros(&self, micros: u64) {
233		mock_time_advance_micros(micros);
234	}
235
236	pub fn advance_millis(&self, millis: u64) {
237		mock_time_advance_millis(millis);
238	}
239
240	pub fn advance(&self, millis: u64) {
241		mock_time_advance_millis(millis);
242	}
243
244	pub fn set_nanos(&self, nanos: u128) {
245		mock_time_set_nanos(nanos);
246	}
247
248	pub fn set_micros(&self, micros: u64) {
249		mock_time_set_micros(micros);
250	}
251
252	pub fn set_millis(&self, millis: u64) {
253		mock_time_set_millis(millis);
254	}
255
256	pub fn set(&self, millis: u64) {
257		mock_time_set_millis(millis);
258	}
259
260	pub fn current_nanos(&self) -> u128 {
261		mock_time_get_nanos().expect("Mock time should be active")
262	}
263
264	pub fn current_micros(&self) -> u64 {
265		mock_time_get_micros().expect("Mock time should be active")
266	}
267
268	pub fn current_millis(&self) -> u64 {
269		mock_time_get_millis().expect("Mock time should be active")
270	}
271
272	pub fn current(&self) -> u64 {
273		self.current_millis()
274	}
275}
276
277/// Run a function with mock time that can be controlled
278#[cfg(debug_assertions)]
279pub fn mock_time_with_control<T>(initial_millis: u64, f: impl FnOnce(&MockTimeControl) -> T) -> T {
280	let _guard = mock_time_scoped_millis(initial_millis);
281	let control = MockTimeControl;
282	f(&control)
283}
284
285#[cfg(test)]
286mod tests {
287	use std::time::Duration;
288
289	use tokio::time::sleep;
290
291	use super::*;
292
293	#[tokio::test]
294	async fn test_system_time() {
295		mock_time_clear(); // Ensure no mock time is set
296
297		let t1 = now_millis();
298		sleep(Duration::from_millis(10)).await;
299		let t2 = now_millis();
300		assert!(t2 >= t1 + 10);
301	}
302
303	#[test]
304	fn test_mock_time_set() {
305		mock_time_set(1000);
306		assert_eq!(now_millis(), 1000);
307		assert!(mock_time_is_active());
308
309		mock_time_clear();
310		assert!(!mock_time_is_active());
311	}
312
313	#[test]
314	fn test_mock_time_advance() {
315		mock_time_set(1000);
316		assert_eq!(now_millis(), 1000);
317
318		mock_time_advance(500);
319		assert_eq!(now_millis(), 1500);
320
321		mock_time_advance(250);
322		assert_eq!(now_millis(), 1750);
323
324		mock_time_clear();
325	}
326
327	#[test]
328	fn test_nanosecond_precision() {
329		// Set time with nanosecond precision
330		mock_time_set_nanos(1_234_567_890_123_456_789);
331
332		assert_eq!(now_nanos(), 1_234_567_890_123_456_789);
333		assert_eq!(now_micros(), 1_234_567_890_123_456);
334		assert_eq!(now_millis(), 1_234_567_890_123);
335
336		let dt = now();
337		assert_eq!(dt.timestamp(), 1_234_567_890);
338		assert_eq!(dt.timestamp_nanos() % 1_000_000_000, 123_456_789);
339
340		mock_time_clear();
341	}
342
343	#[test]
344	fn test_microsecond_precision() {
345		mock_time_set_micros(1_234_567_890_123);
346
347		assert_eq!(now_micros(), 1_234_567_890_123);
348		assert_eq!(now_millis(), 1_234_567_890);
349
350		mock_time_advance_micros(500);
351		assert_eq!(now_micros(), 1_234_567_890_623);
352
353		mock_time_clear();
354	}
355
356	#[test]
357	fn test_datetime_conversion() {
358		mock_time_set_nanos(1_700_000_000_987_654_321);
359
360		let dt = now();
361		assert_eq!(dt.timestamp(), 1_700_000_000);
362		assert_eq!(dt.timestamp_nanos() % 1_000_000_000, 987_654_321);
363		assert_eq!(dt.timestamp_millis(), 1_700_000_000_987);
364
365		mock_time_clear();
366	}
367
368	#[test]
369	fn test_mock_time_scoped() {
370		assert!(!mock_time_is_active());
371
372		{
373			let _guard = mock_time_scoped(2000);
374			assert_eq!(now_millis(), 2000);
375			assert!(mock_time_is_active());
376		}
377
378		assert!(!mock_time_is_active());
379	}
380
381	#[test]
382	fn test_mock_time_with() {
383		let result = mock_time_with(3000, || {
384			assert_eq!(now_millis(), 3000);
385			"test"
386		});
387
388		assert_eq!(result, "test");
389		assert!(!mock_time_is_active());
390	}
391
392	#[test]
393	fn test_mock_time_with_control() {
394		mock_time_with_control(1000, |control| {
395			assert_eq!(control.current(), 1000);
396
397			control.advance(500);
398			assert_eq!(now_millis(), 1500);
399
400			control.set(2000);
401			assert_eq!(now_millis(), 2000);
402
403			control.advance_micros(500_000);
404			assert_eq!(now_millis(), 2500);
405
406			control.advance_nanos(500_000_000);
407			assert_eq!(now_millis(), 3000);
408		});
409
410		assert!(!mock_time_is_active());
411	}
412
413	#[test]
414	fn test_parallel_tests_isolated() {
415		use std::thread;
416
417		let handle1 = thread::spawn(|| {
418			mock_time_with(1000, || {
419				assert_eq!(now_millis(), 1000);
420			});
421		});
422
423		let handle2 = thread::spawn(|| {
424			mock_time_with(2000, || {
425				assert_eq!(now_millis(), 2000);
426			});
427		});
428
429		handle1.join().unwrap();
430		handle2.join().unwrap();
431	}
432}