use serde::{Deserialize, Serialize};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ClockState {
Running,
Paused,
System,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClockOptions {
pub time_ms: u64,
pub paused: bool,
}
impl ClockOptions {
#[must_use]
pub fn now() -> Self {
let time_ms = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0);
Self {
time_ms,
paused: false,
}
}
#[must_use]
pub fn fixed(time_ms: u64) -> Self {
Self {
time_ms,
paused: true,
}
}
pub fn from_iso(iso: &str) -> Result<Self, ClockError> {
let time_ms = parse_iso_to_ms(iso)?;
Ok(Self {
time_ms,
paused: false,
})
}
#[must_use]
pub fn paused(mut self, paused: bool) -> Self {
self.paused = paused;
self
}
}
impl Default for ClockOptions {
fn default() -> Self {
Self::now()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ClockError {
InvalidFormat(String),
NotInstalled,
AlreadyInstalled,
}
impl std::fmt::Display for ClockError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidFormat(s) => write!(f, "Invalid datetime format: {s}"),
Self::NotInstalled => write!(f, "Clock not installed"),
Self::AlreadyInstalled => write!(f, "Clock already installed"),
}
}
}
impl std::error::Error for ClockError {}
#[derive(Debug)]
pub struct FakeClock {
current_ms: AtomicU64,
paused: AtomicBool,
installed: AtomicBool,
install_real_ms: AtomicU64,
install_fake_ms: AtomicU64,
}
impl FakeClock {
#[must_use]
pub fn new() -> Self {
Self {
current_ms: AtomicU64::new(0),
paused: AtomicBool::new(false),
installed: AtomicBool::new(false),
install_real_ms: AtomicU64::new(0),
install_fake_ms: AtomicU64::new(0),
}
}
pub fn install(&self, options: ClockOptions) -> Result<(), ClockError> {
if self.installed.swap(true, Ordering::SeqCst) {
return Err(ClockError::AlreadyInstalled);
}
let real_ms = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0);
self.install_real_ms.store(real_ms, Ordering::SeqCst);
self.install_fake_ms
.store(options.time_ms, Ordering::SeqCst);
self.current_ms.store(options.time_ms, Ordering::SeqCst);
self.paused.store(options.paused, Ordering::SeqCst);
Ok(())
}
pub fn uninstall(&self) {
self.installed.store(false, Ordering::SeqCst);
self.paused.store(false, Ordering::SeqCst);
}
#[must_use]
pub fn is_installed(&self) -> bool {
self.installed.load(Ordering::SeqCst)
}
#[must_use]
pub fn is_paused(&self) -> bool {
self.paused.load(Ordering::SeqCst)
}
#[must_use]
pub fn now_ms(&self) -> u64 {
if !self.is_installed() {
return SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0);
}
if self.is_paused() {
return self.current_ms.load(Ordering::SeqCst);
}
let real_now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0);
let real_elapsed = real_now.saturating_sub(self.install_real_ms.load(Ordering::SeqCst));
let current = self.current_ms.load(Ordering::SeqCst);
current + real_elapsed
}
#[must_use]
pub fn now(&self) -> Duration {
Duration::from_millis(self.now_ms())
}
pub fn pause(&self) {
if self.is_installed() && !self.is_paused() {
let current = self.now_ms();
self.current_ms.store(current, Ordering::SeqCst);
self.paused.store(true, Ordering::SeqCst);
}
}
pub fn resume(&self) {
if self.is_installed() && self.is_paused() {
let real_now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0);
self.install_real_ms.store(real_now, Ordering::SeqCst);
self.paused.store(false, Ordering::SeqCst);
}
}
pub fn set_fixed_time(&self, time_ms: u64) {
self.current_ms.store(time_ms, Ordering::SeqCst);
self.paused.store(true, Ordering::SeqCst);
}
pub fn set_fixed_time_iso(&self, iso: &str) -> Result<(), ClockError> {
let time_ms = parse_iso_to_ms(iso)?;
self.set_fixed_time(time_ms);
Ok(())
}
pub fn fast_forward(&self, duration: Duration) {
let current = self.current_ms.load(Ordering::SeqCst);
let new_time = current + duration.as_millis() as u64;
self.current_ms.store(new_time, Ordering::SeqCst);
}
pub fn fast_forward_ms(&self, ms: u64) {
self.fast_forward(Duration::from_millis(ms));
}
pub fn pause_at(&self, time_ms: u64) {
self.current_ms.store(time_ms, Ordering::SeqCst);
self.paused.store(true, Ordering::SeqCst);
}
#[must_use]
pub fn state(&self) -> ClockState {
if !self.is_installed() {
ClockState::System
} else if self.is_paused() {
ClockState::Paused
} else {
ClockState::Running
}
}
}
impl Default for FakeClock {
fn default() -> Self {
Self::new()
}
}
impl Clone for FakeClock {
fn clone(&self) -> Self {
Self {
current_ms: AtomicU64::new(self.current_ms.load(Ordering::SeqCst)),
paused: AtomicBool::new(self.paused.load(Ordering::SeqCst)),
installed: AtomicBool::new(self.installed.load(Ordering::SeqCst)),
install_real_ms: AtomicU64::new(self.install_real_ms.load(Ordering::SeqCst)),
install_fake_ms: AtomicU64::new(self.install_fake_ms.load(Ordering::SeqCst)),
}
}
}
pub type Clock = Arc<FakeClock>;
#[must_use]
pub fn create_clock() -> Clock {
Arc::new(FakeClock::new())
}
fn parse_iso_to_ms(iso: &str) -> Result<u64, ClockError> {
let iso = iso.trim().trim_end_matches('Z');
let parts: Vec<&str> = if iso.contains('T') {
iso.split('T').collect()
} else {
vec![iso, "00:00:00"]
};
if parts.is_empty() {
return Err(ClockError::InvalidFormat(iso.to_string()));
}
let date_parts: Vec<u32> = parts[0]
.split('-')
.map(|s| s.parse().unwrap_or(0))
.collect();
if date_parts.len() < 3 {
return Err(ClockError::InvalidFormat(iso.to_string()));
}
let year = date_parts[0];
let month = date_parts[1];
let day = date_parts[2];
let (hour, minute, second) = if parts.len() > 1 {
let time_parts: Vec<u32> = parts[1]
.split(':')
.map(|s| s.parse().unwrap_or(0))
.collect();
(
*time_parts.first().unwrap_or(&0),
*time_parts.get(1).unwrap_or(&0),
*time_parts.get(2).unwrap_or(&0),
)
} else {
(0, 0, 0)
};
let days_since_epoch = days_since_unix_epoch(year, month, day);
let seconds = days_since_epoch * 86400
+ u64::from(hour) * 3600
+ u64::from(minute) * 60
+ u64::from(second);
Ok(seconds * 1000)
}
fn days_since_unix_epoch(year: u32, month: u32, day: u32) -> u64 {
let mut days: i64 = 0;
for y in 1970..year {
days += if is_leap_year(y) { 366 } else { 365 };
}
let month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
for m in 1..month {
days += i64::from(month_days[(m - 1) as usize]);
if m == 2 && is_leap_year(year) {
days += 1;
}
}
days += i64::from(day - 1);
days.max(0) as u64
}
fn is_leap_year(year: u32) -> bool {
(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
}
#[derive(Debug, Clone)]
pub struct ClockController {
clock: Clock,
}
impl ClockController {
#[must_use]
pub fn new() -> Self {
Self {
clock: create_clock(),
}
}
#[must_use]
pub fn with_clock(clock: Clock) -> Self {
Self { clock }
}
pub fn install(&self, options: ClockOptions) -> Result<(), ClockError> {
self.clock.install(options)
}
pub fn uninstall(&self) {
self.clock.uninstall();
}
pub fn fast_forward(&self, duration: Duration) {
self.clock.fast_forward(duration);
}
pub fn set_fixed_time(&self, time_ms: u64) {
self.clock.set_fixed_time(time_ms);
}
pub fn set_fixed_time_iso(&self, iso: &str) -> Result<(), ClockError> {
self.clock.set_fixed_time_iso(iso)
}
pub fn pause_at(&self, time_ms: u64) {
self.clock.pause_at(time_ms);
}
pub fn pause(&self) {
self.clock.pause();
}
pub fn resume(&self) {
self.clock.resume();
}
#[must_use]
pub fn now_ms(&self) -> u64 {
self.clock.now_ms()
}
#[must_use]
pub fn state(&self) -> ClockState {
self.clock.state()
}
#[must_use]
pub fn inner(&self) -> &Clock {
&self.clock
}
}
impl Default for ClockController {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn h0_clock_01_new() {
let clock = FakeClock::new();
assert!(!clock.is_installed());
assert!(!clock.is_paused());
}
#[test]
fn h0_clock_02_state_system_when_not_installed() {
let clock = FakeClock::new();
assert_eq!(clock.state(), ClockState::System);
}
#[test]
fn h0_clock_03_install_success() {
let clock = FakeClock::new();
let options = ClockOptions::fixed(1_000_000);
clock.install(options).unwrap();
assert!(clock.is_installed());
assert!(clock.is_paused());
}
#[test]
fn h0_clock_04_install_already_installed() {
let clock = FakeClock::new();
clock.install(ClockOptions::now()).unwrap();
let result = clock.install(ClockOptions::now());
assert!(matches!(result, Err(ClockError::AlreadyInstalled)));
}
#[test]
fn h0_clock_05_uninstall() {
let clock = FakeClock::new();
clock.install(ClockOptions::now()).unwrap();
clock.uninstall();
assert!(!clock.is_installed());
}
#[test]
fn h0_clock_06_now_ms_when_paused() {
let clock = FakeClock::new();
clock
.install(ClockOptions::fixed(1_705_312_800_000))
.unwrap();
let time = clock.now_ms();
assert_eq!(time, 1_705_312_800_000);
}
#[test]
fn h0_clock_07_now_returns_duration() {
let clock = FakeClock::new();
clock.install(ClockOptions::fixed(1000)).unwrap();
let duration = clock.now();
assert_eq!(duration.as_millis(), 1000);
}
#[test]
fn h0_clock_08_fast_forward() {
let clock = FakeClock::new();
clock.install(ClockOptions::fixed(1000)).unwrap();
clock.fast_forward(Duration::from_secs(60));
assert_eq!(clock.now_ms(), 61_000);
}
#[test]
fn h0_clock_09_fast_forward_ms() {
let clock = FakeClock::new();
clock.install(ClockOptions::fixed(0)).unwrap();
clock.fast_forward_ms(5000);
assert_eq!(clock.now_ms(), 5000);
}
#[test]
fn h0_clock_10_pause() {
let clock = FakeClock::new();
clock
.install(ClockOptions {
time_ms: 1000,
paused: false,
})
.unwrap();
clock.pause();
assert!(clock.is_paused());
assert_eq!(clock.state(), ClockState::Paused);
}
#[test]
fn h0_clock_11_resume() {
let clock = FakeClock::new();
clock.install(ClockOptions::fixed(1000)).unwrap();
clock.resume();
assert!(!clock.is_paused());
assert_eq!(clock.state(), ClockState::Running);
}
#[test]
fn h0_clock_12_pause_at() {
let clock = FakeClock::new();
clock.install(ClockOptions::now()).unwrap();
clock.pause_at(5000);
assert!(clock.is_paused());
assert_eq!(clock.now_ms(), 5000);
}
#[test]
fn h0_clock_13_set_fixed_time() {
let clock = FakeClock::new();
clock.install(ClockOptions::now()).unwrap();
clock.set_fixed_time(9999);
assert!(clock.is_paused());
assert_eq!(clock.now_ms(), 9999);
}
#[test]
fn h0_clock_14_set_fixed_time_iso() {
let clock = FakeClock::new();
clock.install(ClockOptions::now()).unwrap();
clock.set_fixed_time_iso("2024-01-15T12:00:00Z").unwrap();
assert!(clock.is_paused());
assert!(clock.now_ms() > 1_705_000_000_000);
}
#[test]
fn h0_clock_15_options_now() {
let options = ClockOptions::now();
assert!(!options.paused);
assert!(options.time_ms > 0);
}
#[test]
fn h0_clock_16_options_fixed() {
let options = ClockOptions::fixed(1234);
assert!(options.paused);
assert_eq!(options.time_ms, 1234);
}
#[test]
fn h0_clock_17_options_from_iso() {
let options = ClockOptions::from_iso("2024-01-01T00:00:00Z").unwrap();
assert!(options.time_ms > 1_704_000_000_000);
}
#[test]
fn h0_clock_18_options_paused_builder() {
let options = ClockOptions::now().paused(true);
assert!(options.paused);
}
#[test]
fn h0_clock_19_parse_iso_full() {
let ms = parse_iso_to_ms("2024-01-15T10:30:00Z").unwrap();
assert!(ms > 1_705_000_000_000);
}
#[test]
fn h0_clock_20_parse_iso_date_only() {
let ms = parse_iso_to_ms("2024-01-15").unwrap();
assert!(ms > 1_705_000_000_000);
}
#[test]
fn h0_clock_21_parse_iso_invalid() {
let result = parse_iso_to_ms("invalid");
assert!(result.is_err());
}
#[test]
fn h0_clock_22_controller_new() {
let controller = ClockController::new();
assert_eq!(controller.state(), ClockState::System);
}
#[test]
fn h0_clock_23_controller_install() {
let controller = ClockController::new();
controller.install(ClockOptions::fixed(1000)).unwrap();
assert_eq!(controller.state(), ClockState::Paused);
assert_eq!(controller.now_ms(), 1000);
}
#[test]
fn h0_clock_24_controller_fast_forward() {
let controller = ClockController::new();
controller.install(ClockOptions::fixed(0)).unwrap();
controller.fast_forward(Duration::from_secs(30));
assert_eq!(controller.now_ms(), 30_000);
}
#[test]
fn h0_clock_25_controller_pause_resume() {
let controller = ClockController::new();
controller
.install(ClockOptions {
time_ms: 1000,
paused: false,
})
.unwrap();
controller.pause();
assert_eq!(controller.state(), ClockState::Paused);
controller.resume();
assert_eq!(controller.state(), ClockState::Running);
}
#[test]
fn h0_clock_26_clone() {
let clock = FakeClock::new();
clock.install(ClockOptions::fixed(5000)).unwrap();
let cloned = clock;
assert!(cloned.is_installed());
assert_eq!(cloned.now_ms(), 5000);
}
#[test]
fn h0_clock_27_is_leap_year() {
assert!(is_leap_year(2000));
assert!(is_leap_year(2024));
assert!(!is_leap_year(2023));
assert!(!is_leap_year(1900));
}
#[test]
fn h0_clock_28_error_display() {
let err = ClockError::InvalidFormat("bad".to_string());
assert!(err.to_string().contains("Invalid datetime"));
let err = ClockError::NotInstalled;
assert!(err.to_string().contains("not installed"));
let err = ClockError::AlreadyInstalled;
assert!(err.to_string().contains("already installed"));
}
#[test]
fn h0_clock_29_create_clock() {
let clock = create_clock();
assert!(!clock.is_installed());
}
#[test]
fn h0_clock_30_controller_with_clock() {
let clock = create_clock();
clock.install(ClockOptions::fixed(1234)).unwrap();
let controller = ClockController::with_clock(clock);
assert_eq!(controller.now_ms(), 1234);
}
#[test]
fn h0_clock_31_options_default() {
let options = ClockOptions::default();
assert!(options.time_ms > 0 || !options.paused);
}
#[test]
fn test_clock_controller_inner() {
let controller = ClockController::new();
let inner = controller.inner();
assert!(!inner.is_installed());
}
#[test]
fn test_clock_controller_uninstall() {
let controller = ClockController::new();
controller.install(ClockOptions::fixed(1000)).unwrap();
assert_eq!(controller.state(), ClockState::Paused);
controller.uninstall();
assert_eq!(controller.state(), ClockState::System);
}
#[test]
fn test_clock_controller_pause_at() {
let controller = ClockController::new();
controller.install(ClockOptions::now()).unwrap();
controller.pause_at(5000);
assert_eq!(controller.state(), ClockState::Paused);
assert_eq!(controller.now_ms(), 5000);
}
#[test]
fn test_clock_controller_set_fixed_time() {
let controller = ClockController::new();
controller.install(ClockOptions::now()).unwrap();
controller.set_fixed_time(9999);
assert_eq!(controller.now_ms(), 9999);
}
#[test]
fn test_clock_controller_set_fixed_time_iso() {
let controller = ClockController::new();
controller.install(ClockOptions::now()).unwrap();
controller
.set_fixed_time_iso("2024-06-15T12:30:00Z")
.unwrap();
assert!(controller.now_ms() > 1_718_000_000_000);
}
#[test]
fn test_clock_controller_set_fixed_time_iso_error() {
let controller = ClockController::new();
controller.install(ClockOptions::now()).unwrap();
let result = controller.set_fixed_time_iso("invalid");
assert!(result.is_err());
}
#[test]
fn test_fake_clock_now_ms_not_installed() {
let clock = FakeClock::new();
let time = clock.now_ms();
let system_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0);
assert!((time as i64 - system_time as i64).abs() < 1000);
}
#[test]
fn test_fake_clock_now_ms_running() {
let clock = FakeClock::new();
clock
.install(ClockOptions {
time_ms: 1000,
paused: false, })
.unwrap();
std::thread::sleep(std::time::Duration::from_millis(50));
let time = clock.now_ms();
assert!(time >= 1040);
}
#[test]
fn test_fake_clock_pause_not_installed() {
let clock = FakeClock::new();
clock.pause();
assert!(!clock.is_paused());
}
#[test]
fn test_fake_clock_resume_not_installed() {
let clock = FakeClock::new();
clock.resume();
assert!(!clock.is_paused());
}
#[test]
fn test_fake_clock_pause_already_paused() {
let clock = FakeClock::new();
clock.install(ClockOptions::fixed(1000)).unwrap();
clock.pause(); assert!(clock.is_paused());
assert_eq!(clock.now_ms(), 1000);
}
#[test]
fn test_fake_clock_resume_not_paused() {
let clock = FakeClock::new();
clock
.install(ClockOptions {
time_ms: 1000,
paused: false,
})
.unwrap();
clock.resume(); assert!(!clock.is_paused());
}
#[test]
fn test_fake_clock_clone() {
let clock = FakeClock::new();
clock.install(ClockOptions::fixed(5000)).unwrap();
let cloned = clock;
assert!(cloned.is_installed());
assert!(cloned.is_paused());
assert_eq!(cloned.now_ms(), 5000);
}
#[test]
fn test_clock_options_serialize_deserialize() {
let options = ClockOptions {
time_ms: 1234567890,
paused: true,
};
let json = serde_json::to_string(&options).unwrap();
let deserialized: ClockOptions = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.time_ms, 1234567890);
assert!(deserialized.paused);
}
#[test]
fn test_clock_state_serialize_deserialize() {
let state = ClockState::Paused;
let json = serde_json::to_string(&state).unwrap();
let deserialized: ClockState = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, ClockState::Paused);
let state2 = ClockState::Running;
let json2 = serde_json::to_string(&state2).unwrap();
let deserialized2: ClockState = serde_json::from_str(&json2).unwrap();
assert_eq!(deserialized2, ClockState::Running);
}
#[test]
fn test_clock_error_display_invalid_format() {
let err = ClockError::InvalidFormat("bad date".to_string());
let display = err.to_string();
assert!(display.contains("Invalid datetime format"));
assert!(display.contains("bad date"));
}
#[test]
fn test_clock_error_is_error() {
let err: &dyn std::error::Error = &ClockError::NotInstalled;
assert!(err.to_string().contains("not installed"));
}
#[test]
fn test_parse_iso_date_only_without_t() {
let ms = parse_iso_to_ms("2024-03-15").unwrap();
assert!(ms > 1_710_000_000_000);
}
#[test]
fn test_parse_iso_with_trailing_z() {
let ms1 = parse_iso_to_ms("2024-01-15T10:30:00Z").unwrap();
let ms2 = parse_iso_to_ms("2024-01-15T10:30:00").unwrap();
assert_eq!(ms1, ms2);
}
#[test]
fn test_parse_iso_with_whitespace() {
let ms = parse_iso_to_ms(" 2024-01-15T10:30:00Z ").unwrap();
assert!(ms > 1_705_000_000_000);
}
#[test]
fn test_parse_iso_partial_time() {
let ms = parse_iso_to_ms("2024-01-15T10").unwrap();
assert!(ms > 1_705_000_000_000);
}
#[test]
fn test_days_since_unix_epoch() {
let days = days_since_unix_epoch(1970, 1, 1);
assert_eq!(days, 0);
let days = days_since_unix_epoch(1970, 1, 2);
assert_eq!(days, 1);
let days = days_since_unix_epoch(1971, 1, 1);
assert_eq!(days, 365);
}
#[test]
fn test_days_since_unix_epoch_leap_year() {
let days_2000 = days_since_unix_epoch(2000, 12, 31);
let days_2001 = days_since_unix_epoch(2001, 1, 1);
assert_eq!(days_2001 - days_2000, 1);
let start_2000 = days_since_unix_epoch(2000, 1, 1);
let start_2001 = days_since_unix_epoch(2001, 1, 1);
assert_eq!(start_2001 - start_2000, 366);
}
#[test]
fn test_is_leap_year_comprehensive() {
assert!(is_leap_year(2020));
assert!(is_leap_year(2024));
assert!(is_leap_year(2028));
assert!(!is_leap_year(2019));
assert!(!is_leap_year(2021));
assert!(!is_leap_year(2023));
assert!(!is_leap_year(1900)); assert!(!is_leap_year(2100));
assert!(is_leap_year(2000)); }
#[test]
fn test_clock_controller_default() {
let controller = ClockController::default();
assert_eq!(controller.state(), ClockState::System);
}
#[test]
fn test_fake_clock_default() {
let clock = FakeClock::default();
assert!(!clock.is_installed());
assert!(!clock.is_paused());
}
#[test]
fn test_fast_forward_preserves_paused_state() {
let clock = FakeClock::new();
clock.install(ClockOptions::fixed(1000)).unwrap();
assert!(clock.is_paused());
clock.fast_forward_ms(500);
assert_eq!(clock.now_ms(), 1500);
assert!(clock.is_paused());
}
#[test]
fn test_set_fixed_time_pauses_clock() {
let clock = FakeClock::new();
clock
.install(ClockOptions {
time_ms: 1000,
paused: false,
})
.unwrap();
assert!(!clock.is_paused());
clock.set_fixed_time(5000);
assert!(clock.is_paused());
assert_eq!(clock.now_ms(), 5000);
}
#[test]
fn test_clock_state_equality() {
assert_eq!(ClockState::Running, ClockState::Running);
assert_eq!(ClockState::Paused, ClockState::Paused);
assert_eq!(ClockState::System, ClockState::System);
assert_ne!(ClockState::Running, ClockState::Paused);
}
#[test]
fn test_parse_iso_short_date() {
let result = parse_iso_to_ms("2024-01");
assert!(result.is_err());
}
}