clock_core/
stopwatch.rs

1//! # Stopwatch
2//!
3//! A stopwatch that mimics iOS's stopwatch.
4//!
5//! ## Usage
6//!
7//! - Use `Stopwatch::new()` to initialise a new stopwatch instance. The stopwatch is paused
8//! at `00:00` and will **not** run until you call `.resume()` or `.pause_or_resume()`.
9//! - While running:
10//!     - Call `.lap()` to record lap times.
11//!     - Call `.pause_or_resume()`, `.pause()` or `.resume()` to pause or resume.
12//! - When you want to stop (reset), call `.stop()`, which resets the stopwatch and returns
13//!   [`StopwatchData`](struct.StopwatchData.html)
14//!
15//! ## Examples
16//!
17//! ## Schematic
18//!
19//! ```ignore
20//!                  lap    lap          lap
21//! start       start |      |     start  |
22//!   o--------x   o-----------x      o-----------x
23//!          pause           pause            pause(end)
24//! ```
25
26use chrono::{DateTime, Duration, Local};
27use std::{default::Default, mem};
28
29#[derive(Debug)]
30/// The data returned by [`Stopwatch`](struct.Stopwatch.html) upon `.stop`ping (i.e. resetting)
31pub struct StopwatchData {
32    pub elapsed: Duration,
33    pub pause_moments: Vec<DateTime<Local>>, // moments at which the stopwatch is paused
34    pub start_moments: Vec<DateTime<Local>>, // moments at which the stopwatch resumes
35    pub lap_moments: Vec<DateTime<Local>>,   // moments at which a lap time is read
36    pub laps: Vec<Duration>,                 // lap times
37}
38
39impl Default for StopwatchData {
40    fn default() -> Self {
41        Self {
42            elapsed: Duration::zero(),
43            start_moments: Vec::new(),
44            pause_moments: Vec::new(),
45            lap_moments: Vec::new(),
46            laps: Vec::new(),
47        }
48    }
49}
50
51impl StopwatchData {
52    fn new() -> Self {
53        Self::default()
54    }
55    pub fn start(&self) -> DateTime<Local> {
56        self.start_moments[0]
57    }
58    pub fn stop(&self) -> DateTime<Local> {
59        self.pause_moments[self.pause_moments.len() - 1]
60    }
61}
62
63#[derive(Debug)]
64pub struct Stopwatch {
65    pub lap_elapsed: Duration, // elapsed time of the current lap
66    pub paused: bool,
67    pub data: StopwatchData,
68}
69
70impl Default for Stopwatch {
71    fn default() -> Self {
72        Self {
73            lap_elapsed: Duration::zero(),
74            paused: true, // stopped by default; start by explicitly calling `.resume()`
75            data: StopwatchData::new(),
76        }
77    }
78}
79
80impl Stopwatch {
81    /// initialise a new stopwatch instance.
82    /// The stopwatch is paused at zero and will **not** run until you call `.resume()`
83    /// or `.pause_or_resume()`.
84    pub fn new() -> Self {
85        Self::default()
86    }
87    /// Read the total time elapsed
88    pub fn read(&self) -> Duration {
89        if self.paused {
90            self.data.elapsed
91        } else {
92            self.data.elapsed + (Local::now() - self.last_start())
93        }
94    }
95    /// Pause or resume the timer.
96    pub fn pause_or_resume(&mut self) {
97        self.pause_or_resume_at(Local::now());
98    }
99
100    pub fn pause_or_resume_at(&mut self, moment: DateTime<Local>) {
101        if self.paused {
102            self.resume_at(moment);
103        } else {
104            self.pause_at(moment);
105        }
106    }
107    /// Lap the stopwatch. If the stopwatch is running, return `Some(<laptime>)`.
108    /// If the stopwatch is paused, return `None`.
109    pub fn lap(&mut self) -> Option<Duration> {
110        self.lap_at(Local::now())
111    }
112
113    pub fn lap_at(&mut self, moment: DateTime<Local>) -> Option<Duration> {
114        // assert!(!self.paused, "Paused!");
115        if self.paused {
116            None
117        } else {
118            let lap = self.read_lap_elapsed(moment);
119            self.data.lap_moments.push(moment);
120            self.data.laps.push(lap);
121            self.lap_elapsed = Duration::zero();
122            Some(lap)
123        }
124    }
125
126    /// resets the stopwatch and returns [`StopwatchData`](struct.StopwatchData.html)
127    pub fn stop(&mut self) -> StopwatchData {
128        self.stop_at(Local::now())
129    }
130
131    pub fn stop_at(&mut self, moment: DateTime<Local>) -> StopwatchData {
132        // lap
133        let lap = self.read_lap_elapsed(moment);
134        self.data.lap_moments.push(moment);
135        self.data.laps.push(lap);
136        self.lap_elapsed = Duration::zero();
137        // pause
138        self.data.pause_moments.push(moment);
139        self.data.elapsed = self.data.elapsed + (moment - self.last_start());
140        self.paused = true;
141        // data
142        let data = mem::replace(&mut self.data, StopwatchData::new());
143        data
144    }
145
146    /// Read the time elapsed in the current lap
147    fn read_lap_elapsed(&self, moment: DateTime<Local>) -> Duration {
148        self.lap_elapsed
149            + if self.lap_elapsed == Duration::zero() && !self.data.lap_moments.is_empty() {
150                moment - self.last_lap()
151            } else {
152                moment - self.last_start()
153            }
154    }
155
156    fn last_start(&self) -> DateTime<Local> {
157        self.data.start_moments[self.data.start_moments.len() - 1]
158    }
159    fn last_lap(&self) -> DateTime<Local> {
160        self.data.lap_moments[self.data.lap_moments.len() - 1]
161    }
162    /// Pause the stopwatch (suggest using `pause_or_resume` instead.)
163    pub fn pause(&mut self) {
164        self.pause_at(Local::now());
165    }
166    /// Resume the stopwatch (suggest using `pause_or_resume` instead.)
167    pub fn resume(&mut self) {
168        self.resume_at(Local::now());
169    }
170
171    pub fn pause_at(&mut self, moment: DateTime<Local>) {
172        self.data.pause_moments.push(moment);
173        self.data.elapsed = self.data.elapsed + (moment - self.last_start());
174        self.lap_elapsed = self.read_lap_elapsed(moment);
175        self.paused = true;
176    }
177
178    pub fn resume_at(&mut self, moment: DateTime<Local>) {
179        self.data.start_moments.push(moment);
180        self.paused = false;
181    }
182}