1use rand::{Rng, RngExt as _};
4use std::time::{Duration, SystemTime};
5
6pub const NANOS_PER_SEC: u128 = 1_000_000_000;
8
9pub const MAX_DURATION_SINCE_UNIX_EPOCH: Duration = Duration::new(i64::MAX as u64, 999_999_999);
16
17pub const SYSTEM_TIME_PRECISION: Duration = Duration::from_nanos(1);
19
20pub trait DurationExt {
22 fn from_nanos_saturating(ns: u128) -> Duration;
25
26 fn parse(s: &str) -> Result<Duration, String>;
76}
77
78impl DurationExt for Duration {
79 fn from_nanos_saturating(ns: u128) -> Duration {
80 if ns > Self::MAX.as_nanos() {
82 return Self::MAX;
83 }
84
85 let secs = (ns / NANOS_PER_SEC) as u64;
87 let nanos = (ns % NANOS_PER_SEC) as u32;
88 Self::new(secs, nanos)
89 }
90
91 fn parse(s: &str) -> Result<Duration, String> {
92 let s = s.trim();
93
94 if let Some(num_str) = s.strip_suffix("ms") {
96 let millis: u64 = num_str
97 .trim()
98 .parse()
99 .map_err(|_| format!("Invalid milliseconds value: '{num_str}'"))?;
100 return Ok(Self::from_millis(millis));
101 }
102
103 if let Some(num_str) = s.strip_suffix("h") {
105 let hours: u64 = num_str
106 .trim()
107 .parse()
108 .map_err(|_| format!("Invalid hours value: '{num_str}'"))?;
109 let seconds = hours
110 .checked_mul(3600)
111 .ok_or_else(|| format!("Hours value too large (would overflow): '{hours}'"))?;
112 return Ok(Self::from_secs(seconds));
113 }
114
115 if let Some(num_str) = s.strip_suffix("m") {
117 let minutes: u64 = num_str
118 .trim()
119 .parse()
120 .map_err(|_| format!("Invalid minutes value: '{num_str}'"))?;
121 let seconds = minutes
122 .checked_mul(60)
123 .ok_or_else(|| format!("Minutes value too large (would overflow): '{minutes}'"))?;
124 return Ok(Self::from_secs(seconds));
125 }
126
127 if let Some(num_str) = s.strip_suffix("s") {
129 let secs: u64 = num_str
130 .trim()
131 .parse()
132 .map_err(|_| format!("Invalid seconds value: '{num_str}'"))?;
133 return Ok(Self::from_secs(secs));
134 }
135
136 Err(format!(
138 "Invalid duration format: '{s}'. A suffix is required. \
139 Supported formats: '123ms', '30s', '5m', '2h'"
140 ))
141 }
142}
143
144pub trait SystemTimeExt {
146 fn epoch(&self) -> Duration;
150
151 fn epoch_millis(&self) -> u64;
156
157 fn add_jittered(&self, rng: &mut impl Rng, jitter: Duration) -> SystemTime;
160
161 fn limit() -> SystemTime;
163
164 fn saturating_add_ext(&self, delta: Duration) -> SystemTime;
166}
167
168impl SystemTimeExt for SystemTime {
169 fn epoch(&self) -> Duration {
170 self.duration_since(std::time::UNIX_EPOCH)
171 .expect("failed to get epoch time")
172 }
173
174 fn epoch_millis(&self) -> u64 {
175 self.epoch().as_millis().min(u64::MAX as u128) as u64
176 }
177
178 fn add_jittered(&self, rng: &mut impl Rng, jitter: Duration) -> SystemTime {
179 *self + rng.random_range(Duration::default()..=jitter * 2)
180 }
181
182 fn limit() -> SystemTime {
183 Self::UNIX_EPOCH
184 .checked_add(MAX_DURATION_SINCE_UNIX_EPOCH)
185 .expect("maximum system time must be representable")
186 }
187
188 fn saturating_add_ext(&self, delta: Duration) -> SystemTime {
189 if delta.is_zero() {
190 return *self;
191 }
192
193 self.checked_add(delta).unwrap_or_else(Self::limit)
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202 use crate::test_rng;
203
204 #[test]
205 fn test_epoch() {
206 let time = SystemTime::UNIX_EPOCH;
207 assert_eq!(time.epoch(), Duration::from_secs(0));
208
209 let time = SystemTime::UNIX_EPOCH + Duration::from_secs(1) + Duration::from_millis(1);
210 assert_eq!(time.epoch(), Duration::from_millis(1_001));
211 }
212
213 #[test]
214 #[should_panic(expected = "failed to get epoch time")]
215 fn test_epoch_panics() {
216 let time = SystemTime::UNIX_EPOCH - Duration::from_secs(1);
217 time.epoch();
218 }
219
220 #[test]
221 fn test_epoch_millis() {
222 let time = SystemTime::UNIX_EPOCH;
223 assert_eq!(time.epoch_millis(), 0);
224
225 let time = SystemTime::UNIX_EPOCH + Duration::from_secs(1) + Duration::from_millis(1);
226 assert_eq!(time.epoch_millis(), 1_001);
227
228 let time = SystemTime::UNIX_EPOCH + Duration::from_secs(1) + Duration::from_nanos(999_999);
230 assert_eq!(time.epoch_millis(), 1_000);
231
232 let time = SystemTime::UNIX_EPOCH + Duration::from_secs(300);
234 assert_eq!(time.epoch_millis(), 300_000);
235 }
236
237 #[test]
238 #[should_panic(expected = "failed to get epoch time")]
239 fn test_epoch_millis_panics() {
240 let time = SystemTime::UNIX_EPOCH - Duration::from_secs(1);
241 time.epoch_millis();
242 }
243
244 #[test]
245 fn test_from_nanos_saturating() {
246 assert_eq!(Duration::from_nanos_saturating(0), Duration::new(0, 0));
248 assert_eq!(
249 Duration::from_nanos_saturating(NANOS_PER_SEC - 1),
250 Duration::new(0, (NANOS_PER_SEC - 1) as u32)
251 );
252 assert_eq!(
253 Duration::from_nanos_saturating(NANOS_PER_SEC + 1),
254 Duration::new(1, 1)
255 );
256
257 let std = Duration::from_nanos(u64::MAX);
259 let beyond_std = Duration::from_nanos_saturating(u64::MAX as u128 + 1);
260 assert!(beyond_std > std);
261
262 assert_eq!(
264 Duration::from_nanos_saturating(Duration::MAX.as_nanos()),
265 Duration::MAX
266 );
267
268 assert_eq!(
270 Duration::from_nanos_saturating(Duration::MAX.as_nanos() + 1),
271 Duration::MAX
272 );
273 assert_eq!(Duration::from_nanos_saturating(u128::MAX), Duration::MAX);
274 }
275
276 #[test]
277 fn test_add_jittered() {
278 let mut rng = test_rng();
279 let time = SystemTime::UNIX_EPOCH + Duration::from_secs(1);
280 let jitter = Duration::from_secs(2);
281
282 let (mut below, mut above) = (false, false);
284 let avg = time + jitter;
285 for _ in 0..100 {
286 let new_time = time.add_jittered(&mut rng, jitter);
287
288 below |= new_time < avg;
290 above |= new_time > avg;
291
292 assert!(new_time >= time);
294 assert!(new_time <= time + (jitter * 2));
295 }
296 assert!(below && above);
297 }
298
299 #[test]
300 fn check_duration_limit() {
301 let result = SystemTime::limit()
303 .checked_add(SYSTEM_TIME_PRECISION - Duration::from_nanos(1))
304 .expect("addition within precision should round down");
305 assert_eq!(result, SystemTime::limit(), "unexpected precision");
306
307 let result = SystemTime::limit().checked_add(SYSTEM_TIME_PRECISION);
309 assert!(result.is_none(), "able to exceed max duration");
310 }
311
312 #[test]
313 fn system_time_saturating_add() {
314 let max = SystemTime::limit();
315 assert_eq!(max.saturating_add_ext(Duration::from_nanos(1)), max);
316 assert_eq!(max.saturating_add_ext(Duration::from_secs(1)), max);
317 }
318
319 #[test]
320 fn test_duration_parse_milliseconds() {
321 assert_eq!(
322 Duration::parse("500ms").unwrap(),
323 Duration::from_millis(500)
324 );
325 assert_eq!(Duration::parse("0ms").unwrap(), Duration::from_millis(0));
326 assert_eq!(Duration::parse("1ms").unwrap(), Duration::from_millis(1));
327 assert_eq!(
328 Duration::parse("1000ms").unwrap(),
329 Duration::from_millis(1000)
330 );
331 assert_eq!(
332 Duration::parse("250ms").unwrap(),
333 Duration::from_millis(250)
334 );
335 }
336
337 #[test]
338 fn test_duration_parse_seconds() {
339 assert_eq!(Duration::parse("30s").unwrap(), Duration::from_secs(30));
340 assert_eq!(Duration::parse("0s").unwrap(), Duration::from_secs(0));
341 assert_eq!(Duration::parse("1s").unwrap(), Duration::from_secs(1));
342 assert_eq!(Duration::parse("45s").unwrap(), Duration::from_secs(45));
343 assert_eq!(Duration::parse("60s").unwrap(), Duration::from_secs(60));
344 assert_eq!(Duration::parse("3600s").unwrap(), Duration::from_secs(3600));
345 }
346
347 #[test]
348 fn test_duration_parse_minutes() {
349 assert_eq!(Duration::parse("5m").unwrap(), Duration::from_secs(300));
350 assert_eq!(Duration::parse("1m").unwrap(), Duration::from_secs(60));
351 assert_eq!(Duration::parse("0m").unwrap(), Duration::from_secs(0));
352 assert_eq!(Duration::parse("10m").unwrap(), Duration::from_secs(600));
353 assert_eq!(Duration::parse("15m").unwrap(), Duration::from_secs(900));
354 assert_eq!(Duration::parse("30m").unwrap(), Duration::from_secs(1800));
355 assert_eq!(Duration::parse("60m").unwrap(), Duration::from_secs(3600));
356 }
357
358 #[test]
359 fn test_duration_parse_hours() {
360 assert_eq!(Duration::parse("2h").unwrap(), Duration::from_secs(7200));
361 assert_eq!(Duration::parse("1h").unwrap(), Duration::from_secs(3600));
362 assert_eq!(Duration::parse("0h").unwrap(), Duration::from_secs(0));
363 assert_eq!(Duration::parse("3h").unwrap(), Duration::from_secs(10800));
364 assert_eq!(Duration::parse("4h").unwrap(), Duration::from_secs(14400));
365 assert_eq!(Duration::parse("12h").unwrap(), Duration::from_secs(43200));
366 assert_eq!(Duration::parse("24h").unwrap(), Duration::from_secs(86400));
367 assert_eq!(
368 Duration::parse("168h").unwrap(),
369 Duration::from_secs(604800)
370 );
371 }
373
374 #[test]
375 fn test_duration_parse_whitespace() {
376 assert_eq!(Duration::parse(" 30s ").unwrap(), Duration::from_secs(30));
378 assert_eq!(
379 Duration::parse("\t500ms\n").unwrap(),
380 Duration::from_millis(500)
381 );
382 assert_eq!(Duration::parse(" 2h ").unwrap(), Duration::from_secs(7200));
383
384 assert_eq!(Duration::parse("30 s").unwrap(), Duration::from_secs(30));
386 assert_eq!(
387 Duration::parse("500 ms").unwrap(),
388 Duration::from_millis(500)
389 );
390 assert_eq!(Duration::parse("2 h").unwrap(), Duration::from_secs(7200));
391 assert_eq!(Duration::parse("5 m").unwrap(), Duration::from_secs(300));
392 }
393
394 #[test]
395 fn test_duration_parse_error_cases() {
396 assert!(Duration::parse("invalid").is_err());
398 assert!(Duration::parse("abc123ms").is_err());
399 assert!(Duration::parse("12.5s").is_err()); assert!(Duration::parse("10x").is_err());
403 assert!(Duration::parse("30days").is_err());
404 assert!(Duration::parse("5y").is_err());
405
406 assert!(Duration::parse("5minutes").is_err());
408 assert!(Duration::parse("10seconds").is_err());
409 assert!(Duration::parse("2hours").is_err());
410 assert!(Duration::parse("500millis").is_err());
411 assert!(Duration::parse("30sec").is_err());
412 assert!(Duration::parse("5min").is_err());
413 assert!(Duration::parse("2hr").is_err());
414
415 assert!(Duration::parse("60").is_err());
417 assert!(Duration::parse("0").is_err());
418 assert!(Duration::parse("3600").is_err());
419 assert!(Duration::parse("1").is_err());
420
421 assert!(Duration::parse("").is_err());
423 assert!(Duration::parse(" ").is_err());
424
425 assert!(Duration::parse("-5s").is_err());
427 assert!(Duration::parse("-100ms").is_err());
428
429 assert!(Duration::parse("30S").is_err());
431 assert!(Duration::parse("500MS").is_err());
432 assert!(Duration::parse("2H").is_err());
433 }
434
435 #[test]
436 fn test_duration_parse_large_values() {
437 assert_eq!(
439 Duration::parse("999999999ms").unwrap(),
440 Duration::from_millis(999999999)
441 );
442 assert_eq!(
443 Duration::parse("99999999s").unwrap(),
444 Duration::from_secs(99999999)
445 );
446 }
447
448 #[test]
449 fn test_duration_parse_overflow_cases() {
450 let max_safe_hours = u64::MAX / 3600;
452 let overflow_hours = max_safe_hours + 1;
453 assert!(Duration::parse(&format!("{max_safe_hours}h")).is_ok());
454 match Duration::parse(&format!("{overflow_hours}h")) {
455 Err(msg) => assert!(msg.contains("too large (would overflow)")),
456 Ok(_) => panic!("Expected overflow error for large hours value"),
457 }
458 match Duration::parse(&format!("{}h", u64::MAX)) {
459 Err(msg) => assert!(msg.contains("too large (would overflow)")),
460 Ok(_) => panic!("Expected overflow error for u64::MAX hours"),
461 }
462
463 let max_safe_minutes = u64::MAX / 60;
465 let overflow_minutes = max_safe_minutes + 1;
466 assert!(Duration::parse(&format!("{max_safe_minutes}m")).is_ok());
467 match Duration::parse(&format!("{overflow_minutes}m")) {
468 Err(msg) => assert!(msg.contains("too large (would overflow)")),
469 Ok(_) => panic!("Expected overflow error for large minutes value"),
470 }
471 match Duration::parse(&format!("{}m", u64::MAX)) {
472 Err(msg) => assert!(msg.contains("too large (would overflow)")),
473 Ok(_) => panic!("Expected overflow error for u64::MAX minutes"),
474 }
475 }
476}