use chrono::{DateTime, Utc};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
pub trait Clock: Send + Sync {
fn now(&self) -> u64;
fn now_datetime(&self) -> DateTime<Utc>;
fn now_system_time(&self) -> SystemTime;
fn has_elapsed(&self, since: u64, duration: Duration) -> bool {
let elapsed = self.now().saturating_sub(since);
elapsed >= duration.as_secs()
}
fn duration_since(&self, timestamp: u64) -> Duration {
let elapsed_secs = self.now().saturating_sub(timestamp);
Duration::from_secs(elapsed_secs)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct SystemClock;
impl Clock for SystemClock {
fn now(&self) -> u64 {
Utc::now().timestamp() as u64
}
fn now_datetime(&self) -> DateTime<Utc> {
Utc::now()
}
fn now_system_time(&self) -> SystemTime {
SystemTime::now()
}
}
#[derive(Debug, Clone)]
pub struct MockClock {
current_time: u64,
}
impl MockClock {
#[must_use]
pub fn new(start_time: u64) -> Self {
Self {
current_time: start_time,
}
}
#[must_use]
pub fn now() -> Self {
Self::new(SystemClock.now())
}
pub fn advance(&mut self, duration: Duration) {
self.current_time += duration.as_secs();
}
pub fn advance_secs(&mut self, seconds: u64) {
self.current_time += seconds;
}
pub fn set_time(&mut self, timestamp: u64) {
self.current_time = timestamp;
}
pub fn reset(&mut self) {
self.current_time = 0;
}
}
impl Clock for MockClock {
fn now(&self) -> u64 {
self.current_time
}
fn now_datetime(&self) -> DateTime<Utc> {
DateTime::from_timestamp(self.current_time as i64, 0)
.unwrap_or_else(|| DateTime::from_timestamp(0, 0).unwrap())
}
fn now_system_time(&self) -> SystemTime {
UNIX_EPOCH + Duration::from_secs(self.current_time)
}
}
#[must_use]
pub fn create_clock(use_mock: bool, mock_start_time: u64) -> Box<dyn Clock> {
if use_mock {
Box::new(MockClock::new(mock_start_time))
} else {
Box::new(SystemClock)
}
}
pub mod time_utils {
use super::*;
#[must_use]
pub fn format_timestamp(timestamp: u64) -> String {
DateTime::from_timestamp(timestamp as i64, 0)
.map(|dt| dt.format("%Y-%m-%d %H:%M:%S UTC").to_string())
.unwrap_or_else(|| format!("invalid-timestamp-{}", timestamp))
}
#[must_use]
pub fn duration_between(earlier: u64, later: u64) -> Duration {
Duration::from_secs(later.saturating_sub(earlier))
}
pub fn is_recent(timestamp: u64, max_age: Duration, clock: &dyn Clock) -> bool {
let age = clock.duration_since(timestamp);
age <= max_age
}
}