use chrono::{DateTime, Local};
use std::time::{Duration, Instant};
pub(crate) fn stopwatch() -> StopwatchStart {
StopwatchStart::new()
}
#[derive(Clone, Debug)]
pub(crate) struct StopwatchStart {
start_time: DateTime<Local>,
instant: Instant,
paused_time: Duration,
pause_state: StopwatchPauseState,
}
impl StopwatchStart {
fn new() -> Self {
Self {
start_time: Local::now(),
instant: Instant::now(),
paused_time: Duration::ZERO,
pause_state: StopwatchPauseState::Running,
}
}
pub(crate) fn is_paused(&self) -> bool {
matches!(self.pause_state, StopwatchPauseState::Paused { .. })
}
pub(crate) fn pause(&mut self) {
match &self.pause_state {
StopwatchPauseState::Running => {
self.pause_state = StopwatchPauseState::Paused {
paused_at: Instant::now(),
};
}
StopwatchPauseState::Paused { .. } => {
panic!("illegal state transition: pause() called while stopwatch was paused")
}
}
}
pub(crate) fn resume(&mut self) {
match &self.pause_state {
StopwatchPauseState::Paused { paused_at } => {
self.paused_time += paused_at.elapsed();
self.pause_state = StopwatchPauseState::Running;
}
StopwatchPauseState::Running => {
panic!("illegal state transition: resume() called while stopwatch was running")
}
}
}
pub(crate) fn snapshot(&self) -> StopwatchSnapshot {
StopwatchSnapshot {
start_time: self.start_time,
active: self.instant.elapsed().saturating_sub(self.paused_time),
paused: self.paused_time,
}
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct StopwatchSnapshot {
pub(crate) start_time: DateTime<Local>,
pub(crate) active: Duration,
pub(crate) paused: Duration,
}
#[derive(Clone, Debug)]
enum StopwatchPauseState {
Running,
Paused { paused_at: Instant },
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stopwatch_pause() {
let mut start = stopwatch();
let unpaused_start = start.clone();
start.pause();
std::thread::sleep(Duration::from_millis(250));
start.resume();
start.pause();
std::thread::sleep(Duration::from_millis(300));
start.resume();
let end = start.snapshot();
let unpaused_end = unpaused_start.snapshot();
let difference = unpaused_end.active - end.active;
assert!(
difference > Duration::from_millis(450),
"difference between unpaused_end and end ({difference:?}) is at least 450ms"
);
}
}