duration_extender/
lib.rs

1use std::time::Duration;
2
3/// An extension trait that adds fluent time unit methods to integer primitives,
4/// allowing for highly readable time duration creation.
5///
6/// # Panics
7///
8/// - Signed integers (`i32`, `i64`) **panic** if the value is negative.
9/// - All integer types **panic** on overflow when creating a `Duration` (e.g., very large minutes, hours, days, or weeks).
10///
11/// # Examples
12///
13/// ```rust
14/// use duration_extender::DurationExt;
15/// use std::time::Duration;
16///
17/// let timeout = 10.seconds();
18/// let delay = 5.minutes();
19/// let long_wait = 2.days();
20///
21/// let total_time = 2.hours() + 30.minutes() + 15.seconds();
22///
23/// // Signed integers must be non-negative
24/// let elapsed = 100.seconds(); // ✅ Works
25/// // let bad = (-100).seconds(); // ❌ Panics!
26/// ```
27pub trait DurationExt {
28    /// Creates a `Duration` representing this many seconds.
29    fn seconds(self) -> Duration;
30    /// Creates a `Duration` representing this many minutes.
31    fn minutes(self) -> Duration;
32    /// Creates a `Duration` representing this many hours.
33    fn hours(self) -> Duration;
34    /// Creates a `Duration` representing this many days (24 hours).
35    ///
36    /// # Note
37    /// This is a fixed duration of 86,400 seconds. It does **not** account for calendar days or DST.
38    fn days(self) -> Duration;
39    /// Creates a `Duration` representing this many weeks (7 days).
40    ///
41    /// # Note
42    /// This is a fixed duration of 604,800 seconds. It does **not** account for calendar weeks or DST.
43    fn weeks(self) -> Duration;
44    /// Creates a `Duration` representing this many milliseconds.
45    fn milliseconds(self) -> Duration;
46    /// Creates a `Duration` representing this many microseconds.
47    fn microseconds(self) -> Duration;
48    /// Creates a `Duration` representing this many nanoseconds.
49    fn nanoseconds(self) -> Duration;
50}
51
52
53impl DurationExt for u64 {
54    fn seconds(self) -> Duration {
55        Duration::from_secs(self)
56    }
57
58    fn minutes(self) -> Duration {
59        let secs = self.checked_mul(60)
60            .expect(&format!("duration value {} minutes overflows u64 seconds capacity", self));
61        Duration::from_secs(secs)
62    }
63
64    fn hours(self) -> Duration {
65        let secs = self.checked_mul(3600)
66            .expect(&format!("duration value {} hours overflows u64 seconds capacity", self));
67        Duration::from_secs(secs)
68    }
69
70    fn days(self) -> Duration {
71        let secs = self.checked_mul(86400)
72            .expect(&format!("duration value {} days overflows u64 seconds capacity", self));
73        Duration::from_secs(secs)
74    }
75
76    fn weeks(self) -> Duration {
77        let secs = self.checked_mul(604800)
78            .expect(&format!("duration value {} weeks overflows u64 seconds capacity", self));
79        Duration::from_secs(secs)
80    }
81    
82    fn milliseconds(self) -> Duration {
83        Duration::from_millis(self)
84    }
85
86    fn microseconds(self) -> Duration {
87        Duration::from_micros(self)
88    }
89
90    fn nanoseconds(self) -> Duration {
91        Duration::from_nanos(self)
92    }
93}
94
95impl DurationExt for u32 {
96    fn seconds(self) -> Duration {
97        Duration::from_secs(self as u64)
98    }
99
100    fn minutes(self) -> Duration {
101        let secs = (self as u64).checked_mul(60)
102            .expect(&format!("duration value {} minutes overflows u64 seconds capacity", self));
103        Duration::from_secs(secs)
104    }
105
106    fn hours(self) -> Duration {
107        let secs = (self as u64).checked_mul(3600)
108            .expect(&format!("duration value {} hours overflows u64 seconds capacity", self));
109        Duration::from_secs(secs)
110    }
111
112    fn days(self) -> Duration {
113        let secs = (self as u64).checked_mul(86400)
114            .expect(&format!("duration value {} days overflows u64 seconds capacity", self));
115        Duration::from_secs(secs)
116    }
117
118    fn weeks(self) -> Duration {
119        let secs = (self as u64).checked_mul(604800)
120            .expect(&format!("duration value {} weeks overflows u64 seconds capacity", self));
121        Duration::from_secs(secs)
122    }
123
124    fn milliseconds(self) -> Duration {
125        Duration::from_millis(self as u64)
126    }
127
128    fn microseconds(self) -> Duration {
129        Duration::from_micros(self as u64)
130    }
131
132    fn nanoseconds(self) -> Duration {
133        Duration::from_nanos(self as u64)
134    }
135}
136
137impl DurationExt for i64 {
138    fn seconds(self) -> Duration {
139        assert!(self >= 0, "duration cannot be negative: got {} seconds", self);
140        Duration::from_secs(self as u64)
141    }
142
143    fn minutes(self) -> Duration {
144        assert!(self >= 0, "duration cannot be negative: got {} minutes", self);
145        let secs = (self as u64).checked_mul(60)
146            .expect(&format!("duration value {} minutes overflows u64 seconds capacity", self));
147        Duration::from_secs(secs)
148    }
149
150    fn hours(self) -> Duration {
151        assert!(self >= 0, "duration cannot be negative: got {} hours", self);
152        let secs = (self as u64).checked_mul(3600)
153            .expect(&format!("duration value {} hours overflows u64 seconds capacity", self));
154        Duration::from_secs(secs)
155    }
156
157    fn days(self) -> Duration {
158        assert!(self >= 0, "duration cannot be negative: got {} days", self);
159        let secs = (self as u64).checked_mul(86400)
160            .expect(&format!("duration value {} days overflows u64 seconds capacity", self));
161        Duration::from_secs(secs)
162    }
163
164    fn weeks(self) -> Duration {
165        assert!(self >= 0, "duration cannot be negative: got {} weeks", self);
166        let secs = (self as u64).checked_mul(604800)
167            .expect(&format!("duration value {} weeks overflows u64 seconds capacity", self));
168        Duration::from_secs(secs)
169    }
170    
171    fn milliseconds(self) -> Duration {
172        assert!(self >= 0, "duration cannot be negative: got {} milliseconds", self);
173        Duration::from_millis(self as u64)
174    }
175
176    fn microseconds(self) -> Duration {
177        assert!(self >= 0, "duration cannot be negative: got {} microseconds", self);
178        Duration::from_micros(self as u64)
179    }
180
181    fn nanoseconds(self) -> Duration {
182        assert!(self >= 0, "duration cannot be negative: got {} nanoseconds", self);
183        Duration::from_nanos(self as u64)
184    }
185}
186
187impl DurationExt for i32 {
188    fn seconds(self) -> Duration {
189        assert!(self >= 0, "duration cannot be negative: got {} seconds", self);
190        Duration::from_secs(self as u64)
191    }
192
193    fn minutes(self) -> Duration {
194        assert!(self >= 0, "duration cannot be negative: got {} minutes", self);
195        let secs = (self as u64).checked_mul(60)
196            .expect(&format!("duration value {} minutes overflows u64 seconds capacity", self));
197        Duration::from_secs(secs)
198    }
199
200    fn hours(self) -> Duration {
201        assert!(self >= 0, "duration cannot be negative: got {} hours", self);
202        let secs = (self as u64).checked_mul(3600)
203            .expect(&format!("duration value {} hours overflows u64 seconds capacity", self));
204        Duration::from_secs(secs)
205    }
206
207    fn days(self) -> Duration {
208        assert!(self >= 0, "duration cannot be negative: got {} days", self);
209        let secs = (self as u64).checked_mul(86400)
210            .expect(&format!("duration value {} days overflows u64 seconds capacity", self));
211        Duration::from_secs(secs)
212    }
213
214    fn weeks(self) -> Duration {
215        assert!(self >= 0, "duration cannot be negative: got {} weeks", self);
216        let secs = (self as u64).checked_mul(604800)
217            .expect(&format!("duration value {} weeks overflows u64 seconds capacity", self));
218        Duration::from_secs(secs)
219    }
220
221    fn milliseconds(self) -> Duration {
222        assert!(self >= 0, "duration cannot be negative: got {} milliseconds", self);
223        Duration::from_millis(self as u64)
224    }
225
226    fn microseconds(self) -> Duration {
227        assert!(self >= 0, "duration cannot be negative: got {} microseconds", self);
228        Duration::from_micros(self as u64)
229    }
230
231    fn nanoseconds(self) -> Duration {
232        assert!(self >= 0, "duration cannot be negative: got {} nanoseconds", self);
233        Duration::from_nanos(self as u64)
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240    use std::time::Duration;
241
242    // --- U64 Tests ---
243    // The largest number that can be multiplied by 60 without overflowing u64
244    const MAX_FOR_MINUTES: u64 = u64::MAX / 60;
245    // A number one larger than MAX_FOR_MINUTES
246    const OVERFLOW_MINUTES: u64 = MAX_FOR_MINUTES.saturating_add(1);
247
248    #[test]
249    fn test_u64_large_units() {
250        let five: u64 = 5;
251        assert_eq!(five.minutes(), Duration::from_secs(5 * 60));
252    }
253    
254    // New test to ensure overflow now panics
255    #[test]
256    #[should_panic(expected = "overflows u64 seconds capacity")]
257    fn test_u64_minutes_panics_on_overflow() {
258        let _ = OVERFLOW_MINUTES.minutes();
259    }
260
261
262    // --- I64 Tests ---
263    #[test]
264    fn test_i64_positive() {
265        let pos_ten: i64 = 10;
266        assert_eq!(pos_ten.minutes(), Duration::from_secs(600));
267    }
268
269    #[test]
270    #[should_panic(expected = "duration cannot be negative")]
271    fn test_i64_negative_panics() {
272        let neg_ten: i64 = -10;
273        let _ = neg_ten.minutes(); 
274    }
275    
276    // New test to ensure signed overflow now panics
277    #[test]
278    #[should_panic(expected = "overflows u64 seconds capacity")]
279    fn test_i64_minutes_panics_on_overflow() {
280        let _ = (OVERFLOW_MINUTES as i64).minutes();
281    }
282
283   
284    #[test]
285    fn test_max_u64_small_units() {
286        let max_u64 = u64::MAX;
287        // This relies on Duration::from_millis to handle its own limits
288        let small_duration = max_u64.milliseconds();
289        assert!(small_duration.as_millis() > 0);
290    }
291}