1// Copyright 2024 New Vector Ltd.
2// Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
3//
4// SPDX-License-Identifier: AGPL-3.0-only
5// Please see LICENSE in the repository root for full details.
67//! A [`Clock`] is a way to get the current date and time.
8//!
9//! This module defines two implemetation of the [`Clock`] trait:
10//! [`SystemClock`] which uses the system time, and a [`MockClock`], which can
11//! be used and freely manipulated in tests.
1213use std::sync::{Arc, atomic::AtomicI64};
1415use chrono::{DateTime, TimeZone, Utc};
1617/// Represents a clock which can give the current date and time
18pub trait Clock: Sync {
19/// Get the current date and time
20fn now(&self) -> DateTime<Utc>;
21}
2223impl<C: Clock + Send + ?Sized> Clock for Arc<C> {
24fn now(&self) -> DateTime<Utc> {
25 (**self).now()
26 }
27}
2829impl<C: Clock + ?Sized> Clock for Box<C> {
30fn now(&self) -> DateTime<Utc> {
31 (**self).now()
32 }
33}
3435/// A clock which uses the system time
36#[derive(Clone, Default)]
37pub struct SystemClock {
38 _private: (),
39}
4041impl Clock for SystemClock {
42fn now(&self) -> DateTime<Utc> {
43// This is the clock used elsewhere, it's fine to call Utc::now here
44#[allow(clippy::disallowed_methods)]
45Utc::now()
46 }
47}
4849/// A fake clock, which uses a fixed timestamp, and can be advanced with the
50/// [`MockClock::advance`] method.
51pub struct MockClock {
52 timestamp: AtomicI64,
53}
5455impl Default for MockClock {
56fn default() -> Self {
57let datetime = Utc.with_ymd_and_hms(2022, 1, 16, 14, 40, 0).unwrap();
58Self::new(datetime)
59 }
60}
6162impl MockClock {
63/// Create a new clock which starts at the given datetime
64#[must_use]
65pub fn new(datetime: DateTime<Utc>) -> Self {
66let timestamp = AtomicI64::new(datetime.timestamp());
67Self { timestamp }
68 }
6970/// Move the clock forward by the given amount of time
71pub fn advance(&self, duration: chrono::Duration) {
72self.timestamp
73 .fetch_add(duration.num_seconds(), std::sync::atomic::Ordering::Relaxed);
74 }
75}
7677impl Clock for MockClock {
78fn now(&self) -> DateTime<Utc> {
79let timestamp = self.timestamp.load(std::sync::atomic::Ordering::Relaxed);
80 chrono::TimeZone::timestamp_opt(&Utc, timestamp, 0).unwrap()
81 }
82}
8384#[cfg(test)]
85mod tests {
86use chrono::Duration;
8788use super::*;
8990#[test]
91fn test_mocked_clock() {
92let clock = MockClock::default();
9394// Time should be frozen, and give out the same timestamp on each call
95let first = clock.now();
96 std::thread::sleep(std::time::Duration::from_millis(10));
97let second = clock.now();
9899assert_eq!(first, second);
100101// Clock can be advanced by a fixed duration
102clock.advance(Duration::microseconds(10 * 1000 * 1000));
103let third = clock.now();
104assert_eq!(first + Duration::microseconds(10 * 1000 * 1000), third);
105 }
106107#[test]
108fn test_real_clock() {
109let clock = SystemClock::default();
110111// Time should not be frozen
112let first = clock.now();
113 std::thread::sleep(std::time::Duration::from_millis(10));
114let second = clock.now();
115116assert_ne!(first, second);
117assert!(first < second);
118 }
119}