execution_time/
time.rs

1use crate::{FormatFloatValue, FormatIntegerValue, Unit};
2
3// Set a small margin of error for floating-point comparisons.
4const EPSILON: f64 = 1e-10;
5
6/// Represents a time duration split into days, hours, minutes, and seconds.
7///
8/// This struct holds the components of a time duration for formatting and display purposes.
9#[derive(Debug, Default, PartialEq)]
10pub struct Time {
11    pub days: u64,
12    pub hours: u8,
13    pub minutes: u8,
14    pub seconds: f64,
15}
16
17impl Time {
18    /// Formats the time duration into a human-readable string.
19    ///
20    /// This method combines the time components (days, hours, minutes, seconds) into a
21    /// single, formatted string.  
22    ///
23    /// It includes only non-zero components, except for seconds, which are always included.
24    ///
25    /// ### Returns
26    ///
27    /// A formatted time string.
28    pub fn format_time(&self) -> String {
29        let mut parts = Vec::new();
30
31        // Add days to the output if they are greater than 0.
32        if self.days > 0 {
33            parts.push(self.days.format_unit(Unit::Day));
34        }
35
36        // Add hours to the output if they are greater than 0, or if days have already been added.
37        if self.hours > 0 || !parts.is_empty() {
38            parts.push(self.hours.format_unit(Unit::Hour));
39        }
40
41        // Add minutes to the output if they are greater than 0, or if hours or days have already been added.
42        if self.minutes > 0 || !parts.is_empty() {
43            parts.push(self.minutes.format_unit(Unit::Minute));
44        }
45
46        // Determine the number of decimal places to use for seconds.
47        let decimal: usize = self.calculate_decimal();
48
49        // Always add seconds to the output.
50        parts.push(self.seconds.format_float_unit(decimal, Unit::Second));
51
52        parts.join(", ")
53    }
54
55    /// Calculates the appropriate number of decimal places for displaying seconds.
56    ///
57    /// This function determines the number of decimal places to show for the seconds
58    /// value based on its magnitude. It aims to provide a balance between precision
59    /// and readability.
60    fn calculate_decimal(&self) -> usize {
61        let sec = self.seconds;
62
63        if sec < EPSILON {
64            // Handles the case where 'sec' is approximately zero. Show one decimal place.
65            1
66        } else if sec >= 1.0 {
67            // If seconds is greater than or equal to 1, show three decimal places.
68            3
69        } else if sec >= 0.001 {
70            // If seconds is greater than or equal to 0.001, show six decimal places.
71            6
72        } else {
73            // Otherwise, show nine decimal places for higher precision.
74            9
75        }
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82    use crate::duration_extension::DurationExtension;
83    use std::time::Duration;
84
85    #[test]
86    fn times_new() {
87        let duration = Duration::from_secs(86400 + 3600 + 60 + 1); // 1 day, 1 hour, 1 minute, 1 second
88        let time = duration.get_time();
89        assert_eq!(time.days, 1);
90        assert_eq!(time.hours, 1);
91        assert_eq!(time.minutes, 1);
92        assert_eq!(time.seconds, 1.0);
93
94        let duration = Duration::from_secs_f64(3661.5); // 1 hour, 1 minute, 1.5 seconds
95        let time = duration.get_time();
96        assert_eq!(time.days, 0);
97        assert_eq!(time.hours, 1);
98        assert_eq!(time.minutes, 1);
99        assert_eq!(time.seconds, 1.5);
100
101        let duration = Duration::from_secs(0);
102        let time = duration.get_time();
103        assert_eq!(time.days, 0);
104        assert_eq!(time.hours, 0);
105        assert_eq!(time.minutes, 0);
106        assert_eq!(time.seconds, 0.0);
107
108        let duration = Duration::from_secs_f64(2.5 * 86400.0);
109        let time = duration.get_time();
110        assert_eq!(time.days, 2);
111        assert_eq!(time.hours, 12);
112        assert_eq!(time.minutes, 0);
113        assert_eq!(time.seconds, 0.0);
114    }
115
116    #[test]
117    fn times_format() {
118        let time = Time {
119            days: 1,
120            hours: 2,
121            minutes: 3,
122            seconds: 4.567002,
123        };
124        assert_eq!(
125            time.format_time(),
126            "1 day, 2 hours, 3 minutes, 4.567 seconds"
127        );
128
129        let time = Time {
130            days: 0,
131            hours: 2,
132            minutes: 3,
133            seconds: 4.567,
134        };
135        assert_eq!(time.format_time(), "2 hours, 3 minutes, 4.567 seconds");
136
137        let time = Time {
138            days: 0,
139            hours: 0,
140            minutes: 3,
141            seconds: 4.567111,
142        };
143        assert_eq!(time.format_time(), "3 minutes, 4.567 seconds");
144
145        let time = Time {
146            days: 0,
147            hours: 0,
148            minutes: 0,
149            seconds: 4.567000444,
150        };
151        assert_eq!(time.format_time(), "4.567 seconds");
152
153        let time = Time {
154            days: 1,
155            hours: 0,
156            minutes: 0,
157            seconds: 0.0,
158        };
159
160        assert_eq!(time.format_time(), "1 day, 0 hour, 0 minute, 0.0 second");
161
162        let time = Time {
163            days: 1,
164            hours: 2,
165            minutes: 0,
166            seconds: 0.0,
167        };
168        assert_eq!(time.format_time(), "1 day, 2 hours, 0 minute, 0.0 second");
169    }
170
171    #[test]
172    fn times_default() {
173        let time = Time {
174            days: 0,
175            hours: 0,
176            minutes: 0,
177            seconds: 0.0,
178        };
179        let time_default = Time::default();
180
181        assert_eq!(time, time_default);
182        assert_eq!(time.format_time(), "0.0 second");
183    }
184}