Skip to main content

atomic_time/
system_time.rs

1use core::sync::atomic::Ordering;
2use std::time::SystemTime;
3
4use crate::AtomicDuration;
5
6/// An atomic version of [`std::time::SystemTime`].
7#[repr(transparent)]
8pub struct AtomicSystemTime(AtomicDuration);
9
10impl core::fmt::Debug for AtomicSystemTime {
11  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
12    f.debug_tuple("AtomicSystemTime")
13      .field(&self.load(Ordering::SeqCst))
14      .finish()
15  }
16}
17impl From<SystemTime> for AtomicSystemTime {
18  /// # Panics
19  ///
20  /// Panics if the given `SystemTime` value is earlier than [`UNIX_EPOCH`](SystemTime::UNIX_EPOCH).
21  #[inline]
22  fn from(system_time: SystemTime) -> Self {
23    Self::new(system_time)
24  }
25}
26impl AtomicSystemTime {
27  /// Returns the system time corresponding to "now".
28  ///
29  /// # Examples
30  /// ```
31  /// use atomic_time::AtomicSystemTime;
32  ///
33  /// let sys_time = AtomicSystemTime::now();
34  /// ```
35  pub fn now() -> Self {
36    Self::new(SystemTime::now())
37  }
38
39  /// Creates a new `AtomicSystemTime` with the given `SystemTime` value.
40  ///
41  /// # Panics
42  ///
43  /// If the given `SystemTime` value is earlier than the [`UNIX_EPOCH`](SystemTime::UNIX_EPOCH).
44  pub fn new(system_time: SystemTime) -> Self {
45    Self(AtomicDuration::new(
46      system_time.duration_since(SystemTime::UNIX_EPOCH).unwrap(),
47    ))
48  }
49
50  /// Loads a value from the atomic system time.
51  pub fn load(&self, order: Ordering) -> SystemTime {
52    SystemTime::UNIX_EPOCH + self.0.load(order)
53  }
54
55  /// Stores a value into the atomic system time.
56  ///
57  /// # Panics
58  ///
59  /// If the given `SystemTime` value is earlier than the [`UNIX_EPOCH`](SystemTime::UNIX_EPOCH).
60  pub fn store(&self, system_time: SystemTime, order: Ordering) {
61    self.0.store(
62      system_time.duration_since(SystemTime::UNIX_EPOCH).unwrap(),
63      order,
64    )
65  }
66
67  /// Stores a value into the atomic system time, returning the previous value.
68  ///
69  /// # Panics
70  ///
71  /// If the given `SystemTime` value is earlier than the [`UNIX_EPOCH`](SystemTime::UNIX_EPOCH).
72  pub fn swap(&self, system_time: SystemTime, order: Ordering) -> SystemTime {
73    SystemTime::UNIX_EPOCH
74      + self.0.swap(
75        system_time.duration_since(SystemTime::UNIX_EPOCH).unwrap(),
76        order,
77      )
78  }
79
80  /// Stores a value into the atomic system time if the current value is the same as the `current`
81  /// value.
82  ///
83  /// # Panics
84  ///
85  /// If the given `SystemTime` value is earlier than the [`UNIX_EPOCH`](SystemTime::UNIX_EPOCH).
86  pub fn compare_exchange(
87    &self,
88    current: SystemTime,
89    new: SystemTime,
90    success: Ordering,
91    failure: Ordering,
92  ) -> Result<SystemTime, SystemTime> {
93    match self.0.compare_exchange(
94      current.duration_since(SystemTime::UNIX_EPOCH).unwrap(),
95      new.duration_since(SystemTime::UNIX_EPOCH).unwrap(),
96      success,
97      failure,
98    ) {
99      Ok(duration) => Ok(SystemTime::UNIX_EPOCH + duration),
100      Err(duration) => Err(SystemTime::UNIX_EPOCH + duration),
101    }
102  }
103
104  /// Stores a value into the atomic system time if the current value is the same as the `current`
105  /// value.
106  ///
107  /// # Panics
108  ///
109  /// If the given `SystemTime` value is earlier than the [`UNIX_EPOCH`](SystemTime::UNIX_EPOCH).
110  pub fn compare_exchange_weak(
111    &self,
112    current: SystemTime,
113    new: SystemTime,
114    success: Ordering,
115    failure: Ordering,
116  ) -> Result<SystemTime, SystemTime> {
117    match self.0.compare_exchange_weak(
118      current.duration_since(SystemTime::UNIX_EPOCH).unwrap(),
119      new.duration_since(SystemTime::UNIX_EPOCH).unwrap(),
120      success,
121      failure,
122    ) {
123      Ok(duration) => Ok(SystemTime::UNIX_EPOCH + duration),
124      Err(duration) => Err(SystemTime::UNIX_EPOCH + duration),
125    }
126  }
127
128  /// Fetches the value, and applies a function to it that returns an optional
129  /// new value. Returns a `Result` of `Ok(previous_value)` if the function returned `Some(_)`, else
130  /// `Err(previous_value)`.
131  ///
132  /// Note: This may call the function multiple times if the value has been changed from other threads in
133  /// the meantime, as long as the function returns `Some(_)`, but the function will have been applied
134  /// only once to the stored value.
135  ///
136  /// `fetch_update` takes two [`Ordering`] arguments to describe the memory ordering of this operation.
137  /// The first describes the required ordering for when the operation finally succeeds while the second
138  /// describes the required ordering for loads. These correspond to the success and failure orderings of
139  /// [`compare_exchange`] respectively.
140  ///
141  /// Using [`Acquire`](Ordering::Acquire) as success ordering makes the store part
142  /// of this operation [`Relaxed`](Ordering::Relaxed), and using [`Release`](Ordering::Release) makes the final successful load
143  /// [`Relaxed`](Ordering::Relaxed). The (failed) load ordering can only be [`SeqCst`](Ordering::SeqCst), [`Acquire`](Ordering::Acquire) or [`Relaxed`](Ordering::Release)
144  /// and must be equivalent to or weaker than the success ordering.
145  ///
146  /// [`compare_exchange`]: #method.compare_exchange
147  ///
148  /// # Panics
149  ///
150  /// If the given `SystemTime` value is earlier than the [`UNIX_EPOCH`](SystemTime::UNIX_EPOCH).
151  ///
152  /// # Examples
153  ///
154  /// ```rust
155  /// use atomic_time::AtomicSystemTime;
156  /// use std::{time::{Duration, SystemTime}, sync::atomic::Ordering};
157  ///
158  /// let now = SystemTime::now();
159  /// let x = AtomicSystemTime::new(now);
160  /// assert_eq!(x.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |_| None), Err(now));
161  ///
162  /// assert_eq!(x.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |x| Some(x + Duration::from_secs(1))), Ok(now));
163  /// assert_eq!(x.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |x| Some(x + Duration::from_secs(1))), Ok(now + Duration::from_secs(1)));
164  /// assert_eq!(x.load(Ordering::SeqCst), now + Duration::from_secs(2));
165  /// ```
166  pub fn fetch_update<F>(
167    &self,
168    set_order: Ordering,
169    fetch_order: Ordering,
170    mut f: F,
171  ) -> Result<SystemTime, SystemTime>
172  where
173    F: FnMut(SystemTime) -> Option<SystemTime>,
174  {
175    self
176      .0
177      .fetch_update(set_order, fetch_order, |duration| {
178        f(SystemTime::UNIX_EPOCH + duration)
179          .map(|system_time| system_time.duration_since(SystemTime::UNIX_EPOCH).unwrap())
180      })
181      .map(|duration| SystemTime::UNIX_EPOCH + duration)
182      .map_err(|duration| SystemTime::UNIX_EPOCH + duration)
183  }
184
185  /// Returns `true` if operations on values of this type are lock-free.
186  /// If the compiler or the platform doesn't support the necessary
187  /// atomic instructions, global locks for every potentially
188  /// concurrent atomic operation will be used.
189  ///
190  /// # Examples
191  /// ```
192  /// use atomic_time::AtomicSystemTime;
193  ///
194  /// let is_lock_free = AtomicSystemTime::is_lock_free();
195  /// ```
196  #[inline]
197  pub fn is_lock_free() -> bool {
198    AtomicDuration::is_lock_free()
199  }
200
201  /// Consumes the atomic and returns the contained value.
202  ///
203  /// This is safe because passing `self` by value guarantees that no other threads are
204  /// concurrently accessing the atomic data.
205  #[inline]
206  pub fn into_inner(self) -> SystemTime {
207    SystemTime::UNIX_EPOCH + self.0.into_inner()
208  }
209}
210
211#[cfg(feature = "serde")]
212const _: () = {
213  use serde::{Deserialize, Serialize};
214
215  impl Serialize for AtomicSystemTime {
216    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
217      self.load(Ordering::SeqCst).serialize(serializer)
218    }
219  }
220
221  impl<'de> Deserialize<'de> for AtomicSystemTime {
222    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
223      Ok(Self::new(SystemTime::deserialize(deserializer)?))
224    }
225  }
226};
227
228#[cfg(test)]
229mod tests {
230  use super::*;
231  use std::time::Duration;
232
233  #[test]
234  fn test_atomic_system_time_now() {
235    let atomic_time = AtomicSystemTime::now();
236    // As SystemTime::now is very precise, this test needs to be lenient with timing.
237    // Just check that the time is not the UNIX_EPOCH.
238    assert_ne!(atomic_time.load(Ordering::SeqCst), SystemTime::UNIX_EPOCH);
239  }
240
241  #[test]
242  fn test_atomic_system_time_new_and_load() {
243    let now = SystemTime::now();
244    let atomic_time = AtomicSystemTime::new(now);
245    assert_eq!(atomic_time.load(Ordering::SeqCst), now);
246  }
247
248  #[test]
249  fn test_atomic_system_time_store_and_load() {
250    let now = SystemTime::now();
251    let after_one_sec = now + Duration::from_secs(1);
252    let atomic_time = AtomicSystemTime::new(now);
253    atomic_time.store(after_one_sec, Ordering::SeqCst);
254    assert_eq!(atomic_time.load(Ordering::SeqCst), after_one_sec);
255  }
256
257  #[test]
258  fn test_atomic_system_time_swap() {
259    let now = SystemTime::now();
260    let after_one_sec = now + Duration::from_secs(1);
261    let atomic_time = AtomicSystemTime::new(now);
262    let prev_time = atomic_time.swap(after_one_sec, Ordering::SeqCst);
263    assert_eq!(prev_time, now);
264    assert_eq!(atomic_time.load(Ordering::SeqCst), after_one_sec);
265  }
266
267  #[test]
268  fn test_atomic_system_time_compare_exchange_weak() {
269    let now = SystemTime::now();
270    let after_one_sec = now + Duration::from_secs(1);
271    let after_two_secs = now + Duration::from_secs(2);
272    let atomic_time = AtomicSystemTime::new(now);
273
274    // Successful compare_exchange_weak
275    let mut result;
276    loop {
277      result =
278        atomic_time.compare_exchange_weak(now, after_one_sec, Ordering::SeqCst, Ordering::SeqCst);
279      if result.is_ok() {
280        break;
281      }
282    }
283    assert!(result.is_ok());
284    assert_eq!(atomic_time.load(Ordering::SeqCst), after_one_sec);
285
286    // Failed compare_exchange_weak
287    let result =
288      atomic_time.compare_exchange_weak(now, after_two_secs, Ordering::SeqCst, Ordering::SeqCst);
289    assert!(result.is_err());
290    assert_eq!(result.unwrap_err(), after_one_sec);
291  }
292
293  #[test]
294  fn test_atomic_system_time_compare_exchange() {
295    let now = SystemTime::now();
296    let after_one_sec = now + Duration::from_secs(1);
297    let atomic_time = AtomicSystemTime::new(now);
298    let result =
299      atomic_time.compare_exchange(now, after_one_sec, Ordering::SeqCst, Ordering::SeqCst);
300    assert!(result.is_ok());
301    assert_eq!(atomic_time.load(Ordering::SeqCst), after_one_sec);
302  }
303
304  #[test]
305  fn test_atomic_system_time_fetch_update() {
306    let now = SystemTime::now();
307    let atomic_time = AtomicSystemTime::new(now);
308
309    // Update the time by adding 1 second
310    let result = atomic_time.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |prev| {
311      Some(prev + Duration::from_secs(1))
312    });
313    assert!(result.is_ok());
314    assert_eq!(result.unwrap(), now);
315    assert_eq!(
316      atomic_time.load(Ordering::SeqCst),
317      now + Duration::from_secs(1)
318    );
319
320    // Trying an update that returns None, should not change the value
321    let result = atomic_time.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |_| None);
322    assert!(result.is_err());
323    assert_eq!(result.unwrap_err(), now + Duration::from_secs(1));
324    assert_eq!(
325      atomic_time.load(Ordering::SeqCst),
326      now + Duration::from_secs(1)
327    );
328  }
329
330  #[test]
331  #[cfg(feature = "std")]
332  fn test_atomic_system_time_thread_safety() {
333    use std::sync::Arc;
334    use std::thread;
335
336    let atomic_time = Arc::new(AtomicSystemTime::now());
337    let mut handles = vec![];
338
339    for _ in 0..4 {
340      let atomic_clone = atomic_time.clone();
341      let handle = thread::spawn(move || {
342        let current = atomic_clone.load(Ordering::SeqCst);
343        let new = current + Duration::from_secs(1);
344        atomic_clone.store(new, Ordering::SeqCst);
345      });
346      handles.push(handle);
347    }
348
349    for handle in handles {
350      handle.join().unwrap();
351    }
352
353    // This checks that the time has advanced, but it's not precise about how much,
354    // due to the non-deterministic nature of thread execution order and timing.
355    assert!(atomic_time.load(Ordering::SeqCst) > SystemTime::now());
356  }
357
358  #[cfg(feature = "serde")]
359  #[test]
360  fn test_atomic_system_time_serde() {
361    use serde::{Deserialize, Serialize};
362
363    #[derive(Serialize, Deserialize)]
364    struct Test {
365      time: AtomicSystemTime,
366    }
367
368    let now = SystemTime::now();
369    let test = Test {
370      time: AtomicSystemTime::new(now),
371    };
372    let serialized = serde_json::to_string(&test).unwrap();
373    let deserialized: Test = serde_json::from_str(&serialized).unwrap();
374    assert_eq!(deserialized.time.load(Ordering::SeqCst), now,);
375  }
376}