1use std::time::{SystemTime, SystemTimeError, UNIX_EPOCH};
2use time::OffsetDateTime;
3
4fn real_time() -> Result<u64, SystemTimeError> {
5 SystemTime::now()
6 .duration_since(UNIX_EPOCH)
7 .map(|t| t.as_secs())
8}
9
10#[cfg(not(test))]
11pub fn current_time() -> Result<u64, SystemTimeError> {
12 real_time()
13}
14
15pub fn now_utc() -> OffsetDateTime {
16 OffsetDateTime::from_unix_timestamp(current_time().unwrap() as i64).unwrap()
17}
18
19#[cfg(test)]
20mod mocked_time {
21 use super::*;
22
23 use std::cell::Cell;
24
25 thread_local! {
26 static TIMESTAMP: Cell<u64> = Cell::new(0);
27 }
28
29 pub fn current_time() -> Result<u64, SystemTimeError> {
30 TIMESTAMP.with(|ts| {
31 let time = ts.get();
32 if time == 0 { real_time() } else { Ok(time) }
33 })
34 }
35
36 fn set_timestamp(timestamp: u64) -> u64 {
37 TIMESTAMP.with(|ts| {
38 let old = ts.get();
39 ts.set(timestamp);
40 old
41 })
42 }
43
44 pub struct MockTimestamp {
45 old: u64,
46 }
47
48 impl MockTimestamp {
49 pub fn real_time(&self) -> u64 {
51 real_time().unwrap()
52 }
53
54 pub fn old_time(&self) -> u64 {
56 self.old
57 }
58
59 pub fn set_time(&self, timestamp: u64) -> u64 {
63 set_timestamp(timestamp)
64 }
65
66 pub fn add_time(&self, time_delta: i64) -> (u64, u64) {
70 let now = ((current_time().unwrap() as i64) + time_delta) as u64;
71 (now, set_timestamp(now))
72 }
73 }
74
75 impl Drop for MockTimestamp {
76 fn drop(&mut self) {
77 set_timestamp(self.old);
78 }
79 }
80
81 pub fn with_timestamp(timestamp: u64) -> MockTimestamp {
82 MockTimestamp {
83 old: set_timestamp(timestamp),
84 }
85 }
86
87 #[cfg(test)]
88 mod tests {
89 use super::*;
90
91 const MOCKED_TIMESTAMP: u64 = 5_000_000;
92
93 mod current_time {
94 use super::*;
95
96 #[test]
97 fn when_set_timestamp_not_called() {
98 let now = real_time().unwrap();
99
100 assert!(current_time().unwrap() >= now);
101 }
102
103 #[test]
104 fn when_set_timestamp_was_called() {
105 set_timestamp(MOCKED_TIMESTAMP);
106
107 assert_eq!(current_time().unwrap(), MOCKED_TIMESTAMP);
108
109 set_timestamp(0);
110 }
111 }
112
113 mod with_timestamp {
114 use super::*;
115
116 #[test]
117 fn when_resets_when_result_dropped() {
118 let now = real_time().unwrap();
119 let ts = with_timestamp(MOCKED_TIMESTAMP);
120
121 assert_eq!(current_time().unwrap(), MOCKED_TIMESTAMP);
122
123 drop(ts);
124
125 assert!(current_time().unwrap() >= now);
126 }
127
128 #[test]
129 fn when_nested() {
130 let now = real_time().unwrap();
131 {
132 let _ts = with_timestamp(MOCKED_TIMESTAMP);
133
134 assert_eq!(current_time().unwrap(), MOCKED_TIMESTAMP);
135
136 {
137 let _ts = with_timestamp(MOCKED_TIMESTAMP + 1_000);
138
139 assert_eq!(current_time().unwrap(), MOCKED_TIMESTAMP + 1_000);
140 }
141
142 {
143 let _ts = with_timestamp(0);
144
145 assert!(current_time().unwrap() >= now);
146 }
147
148 assert_eq!(current_time().unwrap(), MOCKED_TIMESTAMP);
149 }
150
151 assert!(current_time().unwrap() >= now);
152 }
153 }
154 }
155}
156#[cfg(test)]
157pub use mocked_time::*;