1use chrono::{offset::Utc, DateTime};
2
3pub struct TapTempo {
4 start_datetime: Option<DateTime<Utc>>,
5 tap_count: u128,
6}
7
8impl 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 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 #[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 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 #[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}