tap_tempo/
lib.rs

1use chrono::{offset::Utc, DateTime};
2
3pub struct TapTempo {
4    start_datetime: Option<DateTime<Utc>>,
5    tap_count: u128,
6}
7
8// Maybe use different types to enforce the different states of tapping (new, able to calc)
9impl TapTempo {
10    pub fn new() -> Self {
11        Self {
12            start_datetime: None,
13            tap_count: 0,
14        }
15    }
16
17    pub fn tap(&mut self) -> Option<f64> {
18        self.tap_count += 1;
19
20        if let Some(start_datetime) = self.start_datetime {
21            return TapTempo::tempo(self.tap_count, &start_datetime, &Utc::now());
22        }
23
24        self.start_datetime = Some(Utc::now());
25
26        None
27    }
28
29    pub fn tap_count(&self) -> u128 {
30        self.tap_count
31    }
32
33    pub fn reset(&mut self) {
34        self.start_datetime = None;
35        self.tap_count = 0;
36    }
37
38    fn tempo(
39        tap_count: u128,
40        start_datetime: &DateTime<Utc>,
41        end_datetime: &DateTime<Utc>,
42    ) -> Option<f64> {
43        // The datetime check might need to be some sort of error, but this will do for now
44        if tap_count < 2 || start_datetime > end_datetime {
45            return None;
46        }
47
48        let interval_count = tap_count - 1;
49
50        let duration = *end_datetime - *start_datetime;
51        let duration_in_minutes = duration.num_milliseconds() as f64 / 60_000.0;
52
53        Some(interval_count as f64 / duration_in_minutes)
54    }
55}
56
57impl Default for TapTempo {
58    fn default() -> Self {
59        Self::new()
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66    use chrono::{Duration, TimeZone};
67
68    #[test]
69    fn test_tap_tempo() {
70        let mut tap_tempo = TapTempo::new();
71        assert!(tap_tempo.start_datetime.is_none());
72        assert_eq!(tap_tempo.tap_count, 0);
73
74        let tempo = tap_tempo.tap();
75        assert!(tap_tempo.start_datetime.is_some());
76        assert_eq!(tap_tempo.tap_count, 1);
77        assert!(tempo.is_none());
78
79        let tempo = tap_tempo.tap();
80        assert!(tap_tempo.start_datetime.is_some());
81        assert_eq!(tap_tempo.tap_count, 2);
82        assert!(tempo.is_some());
83    }
84
85    // Largely the same as above (redundant), but a bit stripped down for the README.md
86    #[test]
87    fn test_readme_example() {
88        let mut tap_tempo = TapTempo::new();
89        let tempo = tap_tempo.tap();
90        assert!(tempo.is_none());
91
92        // After some time has passed ...
93
94        let tempo = tap_tempo.tap();
95        assert!(tempo.is_some());
96    }
97
98    #[test]
99    fn test_tap_count_and_reset() {
100        let mut tap_tempo = TapTempo::new();
101
102        assert_eq!(tap_tempo.start_datetime, None);
103        assert_eq!(tap_tempo.tap_count, 0);
104
105        tap_tempo.tap();
106
107        assert!(tap_tempo.start_datetime.is_some());
108        assert_eq!(tap_tempo.tap_count, 1);
109
110        tap_tempo.reset();
111
112        assert_eq!(tap_tempo.start_datetime, None);
113        assert_eq!(tap_tempo.tap_count, 0);
114    }
115
116    // Kind of a bad test name, not sure if it should be written as it is or
117    // `test_start_datetime_lte_end_datetime`
118    #[test]
119    fn test_end_datetime_less_than_start_datetime() {
120        let (start_datetime, end_datetime) = get_start_and_end_test_datetimes();
121        let tempo = TapTempo::tempo(2, &end_datetime, &start_datetime);
122        assert_eq!(tempo, None)
123    }
124
125    #[test]
126    fn test_get_tempo_tap_count_zero() {
127        let (start_datetime, end_datetime) = get_start_and_end_test_datetimes();
128        let tempo = TapTempo::tempo(0, &start_datetime, &end_datetime);
129        assert_eq!(tempo, None)
130    }
131
132    #[test]
133    fn test_get_tempo_tap_count_one() {
134        let (start_datetime, end_datetime) = get_start_and_end_test_datetimes();
135        let tempo = TapTempo::tempo(1, &start_datetime, &end_datetime);
136        assert_eq!(tempo, None)
137    }
138
139    #[test]
140    fn test_get_tempo_tap_count_two() {
141        let (start_datetime, end_datetime) = get_start_and_end_test_datetimes();
142        let tempo = TapTempo::tempo(2, &start_datetime, &end_datetime);
143        assert_eq!(tempo, Some(60.0))
144    }
145
146    fn get_start_and_end_test_datetimes() -> (DateTime<Utc>, DateTime<Utc>) {
147        let start_datetime = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap();
148        let end_datetime = start_datetime + Duration::seconds(1);
149        (start_datetime, end_datetime)
150    }
151}