1use rand::Rng;
4use std::time::{Duration, SystemTime};
5
6pub const NANOS_PER_SEC: u128 = 1_000_000_000;
8
9cfg_if::cfg_if! {
10 if #[cfg(windows)] {
11 pub const MAX_DURATION_SINCE_UNIX_EPOCH: Duration = Duration::new(910_692_730_085, 477_580_700);
18
19 pub const SYSTEM_TIME_PRECISION: Duration = Duration::from_nanos(100);
21 } else { #[cfg(not(windows))]
29 pub const MAX_DURATION_SINCE_UNIX_EPOCH: Duration = Duration::new(i64::MAX as u64, 999_999_999);
30
31 pub const SYSTEM_TIME_PRECISION: Duration = Duration::from_nanos(1);
33 }
34}
35
36pub trait DurationExt {
38 fn from_nanos_saturating(ns: u128) -> Duration;
41
42 fn parse(s: &str) -> Result<Duration, String>;
92}
93
94impl DurationExt for Duration {
95 fn from_nanos_saturating(ns: u128) -> Duration {
96 if ns > Self::MAX.as_nanos() {
98 return Self::MAX;
99 }
100
101 let secs = (ns / NANOS_PER_SEC) as u64;
103 let nanos = (ns % NANOS_PER_SEC) as u32;
104 Self::new(secs, nanos)
105 }
106
107 fn parse(s: &str) -> Result<Duration, String> {
108 let s = s.trim();
109
110 if let Some(num_str) = s.strip_suffix("ms") {
112 let millis: u64 = num_str
113 .trim()
114 .parse()
115 .map_err(|_| format!("Invalid milliseconds value: '{num_str}'"))?;
116 return Ok(Self::from_millis(millis));
117 }
118
119 if let Some(num_str) = s.strip_suffix("h") {
121 let hours: u64 = num_str
122 .trim()
123 .parse()
124 .map_err(|_| format!("Invalid hours value: '{num_str}'"))?;
125 let seconds = hours
126 .checked_mul(3600)
127 .ok_or_else(|| format!("Hours value too large (would overflow): '{hours}'"))?;
128 return Ok(Self::from_secs(seconds));
129 }
130
131 if let Some(num_str) = s.strip_suffix("m") {
133 let minutes: u64 = num_str
134 .trim()
135 .parse()
136 .map_err(|_| format!("Invalid minutes value: '{num_str}'"))?;
137 let seconds = minutes
138 .checked_mul(60)
139 .ok_or_else(|| format!("Minutes value too large (would overflow): '{minutes}'"))?;
140 return Ok(Self::from_secs(seconds));
141 }
142
143 if let Some(num_str) = s.strip_suffix("s") {
145 let secs: u64 = num_str
146 .trim()
147 .parse()
148 .map_err(|_| format!("Invalid seconds value: '{num_str}'"))?;
149 return Ok(Self::from_secs(secs));
150 }
151
152 Err(format!(
154 "Invalid duration format: '{s}'. A suffix is required. \
155 Supported formats: '123ms', '30s', '5m', '2h'"
156 ))
157 }
158}
159
160pub trait SystemTimeExt {
162 fn epoch(&self) -> Duration;
166
167 fn epoch_millis(&self) -> u64;
172
173 fn add_jittered(&self, rng: &mut impl Rng, jitter: Duration) -> SystemTime;
176
177 fn limit() -> SystemTime;
179
180 fn saturating_add(&self, delta: Duration) -> SystemTime;
182}
183
184impl SystemTimeExt for SystemTime {
185 fn epoch(&self) -> Duration {
186 self.duration_since(std::time::UNIX_EPOCH)
187 .expect("failed to get epoch time")
188 }
189
190 fn epoch_millis(&self) -> u64 {
191 self.epoch().as_millis().min(u64::MAX as u128) as u64
192 }
193
194 fn add_jittered(&self, rng: &mut impl Rng, jitter: Duration) -> SystemTime {
195 *self + rng.gen_range(Duration::default()..=jitter * 2)
196 }
197
198 fn limit() -> SystemTime {
199 Self::UNIX_EPOCH
200 .checked_add(MAX_DURATION_SINCE_UNIX_EPOCH)
201 .expect("maximum system time must be representable")
202 }
203
204 fn saturating_add(&self, delta: Duration) -> SystemTime {
205 if delta.is_zero() {
206 return *self;
207 }
208
209 self.checked_add(delta).unwrap_or_else(Self::limit)
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218 use crate::test_rng;
219
220 #[test]
221 fn test_epoch() {
222 let time = SystemTime::UNIX_EPOCH;
223 assert_eq!(time.epoch(), Duration::from_secs(0));
224
225 let time = SystemTime::UNIX_EPOCH + Duration::from_secs(1) + Duration::from_millis(1);
226 assert_eq!(time.epoch(), Duration::from_millis(1_001));
227 }
228
229 #[test]
230 #[should_panic(expected = "failed to get epoch time")]
231 fn test_epoch_panics() {
232 let time = SystemTime::UNIX_EPOCH - Duration::from_secs(1);
233 time.epoch();
234 }
235
236 #[test]
237 fn test_epoch_millis() {
238 let time = SystemTime::UNIX_EPOCH;
239 assert_eq!(time.epoch_millis(), 0);
240
241 let time = SystemTime::UNIX_EPOCH + Duration::from_secs(1) + Duration::from_millis(1);
242 assert_eq!(time.epoch_millis(), 1_001);
243
244 let time = SystemTime::UNIX_EPOCH + Duration::from_secs(1) + Duration::from_nanos(999_999);
246 assert_eq!(time.epoch_millis(), 1_000);
247
248 let time = SystemTime::UNIX_EPOCH + Duration::from_secs(300);
250 assert_eq!(time.epoch_millis(), 300_000);
251 }
252
253 #[test]
254 #[should_panic(expected = "failed to get epoch time")]
255 fn test_epoch_millis_panics() {
256 let time = SystemTime::UNIX_EPOCH - Duration::from_secs(1);
257 time.epoch_millis();
258 }
259
260 #[test]
261 fn test_from_nanos_saturating() {
262 assert_eq!(Duration::from_nanos_saturating(0), Duration::new(0, 0));
264 assert_eq!(
265 Duration::from_nanos_saturating(NANOS_PER_SEC - 1),
266 Duration::new(0, (NANOS_PER_SEC - 1) as u32)
267 );
268 assert_eq!(
269 Duration::from_nanos_saturating(NANOS_PER_SEC + 1),
270 Duration::new(1, 1)
271 );
272
273 let std = Duration::from_nanos(u64::MAX);
275 let beyond_std = Duration::from_nanos_saturating(u64::MAX as u128 + 1);
276 assert!(beyond_std > std);
277
278 assert_eq!(
280 Duration::from_nanos_saturating(Duration::MAX.as_nanos()),
281 Duration::MAX
282 );
283
284 assert_eq!(
286 Duration::from_nanos_saturating(Duration::MAX.as_nanos() + 1),
287 Duration::MAX
288 );
289 assert_eq!(Duration::from_nanos_saturating(u128::MAX), Duration::MAX);
290 }
291
292 #[test]
293 fn test_add_jittered() {
294 let mut rng = test_rng();
295 let time = SystemTime::UNIX_EPOCH + Duration::from_secs(1);
296 let jitter = Duration::from_secs(2);
297
298 let (mut below, mut above) = (false, false);
300 let avg = time + jitter;
301 for _ in 0..100 {
302 let new_time = time.add_jittered(&mut rng, jitter);
303
304 below |= new_time < avg;
306 above |= new_time > avg;
307
308 assert!(new_time >= time);
310 assert!(new_time <= time + (jitter * 2));
311 }
312 assert!(below && above);
313 }
314
315 #[test]
316 fn check_duration_limit() {
317 let result = SystemTime::limit()
319 .checked_add(SYSTEM_TIME_PRECISION - Duration::from_nanos(1))
320 .expect("addition within precision should round down");
321 assert_eq!(result, SystemTime::limit(), "unexpected precision");
322
323 let result = SystemTime::limit().checked_add(SYSTEM_TIME_PRECISION);
325 assert!(result.is_none(), "able to exceed max duration");
326 }
327
328 #[test]
329 fn system_time_saturating_add() {
330 let max = SystemTime::limit();
331 assert_eq!(max.saturating_add(Duration::from_nanos(1)), max);
332 assert_eq!(max.saturating_add(Duration::from_secs(1)), max);
333 }
334
335 #[test]
336 fn test_duration_parse_milliseconds() {
337 assert_eq!(
338 Duration::parse("500ms").unwrap(),
339 Duration::from_millis(500)
340 );
341 assert_eq!(Duration::parse("0ms").unwrap(), Duration::from_millis(0));
342 assert_eq!(Duration::parse("1ms").unwrap(), Duration::from_millis(1));
343 assert_eq!(
344 Duration::parse("1000ms").unwrap(),
345 Duration::from_millis(1000)
346 );
347 assert_eq!(
348 Duration::parse("250ms").unwrap(),
349 Duration::from_millis(250)
350 );
351 }
352
353 #[test]
354 fn test_duration_parse_seconds() {
355 assert_eq!(Duration::parse("30s").unwrap(), Duration::from_secs(30));
356 assert_eq!(Duration::parse("0s").unwrap(), Duration::from_secs(0));
357 assert_eq!(Duration::parse("1s").unwrap(), Duration::from_secs(1));
358 assert_eq!(Duration::parse("45s").unwrap(), Duration::from_secs(45));
359 assert_eq!(Duration::parse("60s").unwrap(), Duration::from_secs(60));
360 assert_eq!(Duration::parse("3600s").unwrap(), Duration::from_secs(3600));
361 }
362
363 #[test]
364 fn test_duration_parse_minutes() {
365 assert_eq!(Duration::parse("5m").unwrap(), Duration::from_secs(300));
366 assert_eq!(Duration::parse("1m").unwrap(), Duration::from_secs(60));
367 assert_eq!(Duration::parse("0m").unwrap(), Duration::from_secs(0));
368 assert_eq!(Duration::parse("10m").unwrap(), Duration::from_secs(600));
369 assert_eq!(Duration::parse("15m").unwrap(), Duration::from_secs(900));
370 assert_eq!(Duration::parse("30m").unwrap(), Duration::from_secs(1800));
371 assert_eq!(Duration::parse("60m").unwrap(), Duration::from_secs(3600));
372 }
373
374 #[test]
375 fn test_duration_parse_hours() {
376 assert_eq!(Duration::parse("2h").unwrap(), Duration::from_secs(7200));
377 assert_eq!(Duration::parse("1h").unwrap(), Duration::from_secs(3600));
378 assert_eq!(Duration::parse("0h").unwrap(), Duration::from_secs(0));
379 assert_eq!(Duration::parse("3h").unwrap(), Duration::from_secs(10800));
380 assert_eq!(Duration::parse("4h").unwrap(), Duration::from_secs(14400));
381 assert_eq!(Duration::parse("12h").unwrap(), Duration::from_secs(43200));
382 assert_eq!(Duration::parse("24h").unwrap(), Duration::from_secs(86400));
383 assert_eq!(
384 Duration::parse("168h").unwrap(),
385 Duration::from_secs(604800)
386 );
387 }
389
390 #[test]
391 fn test_duration_parse_whitespace() {
392 assert_eq!(Duration::parse(" 30s ").unwrap(), Duration::from_secs(30));
394 assert_eq!(
395 Duration::parse("\t500ms\n").unwrap(),
396 Duration::from_millis(500)
397 );
398 assert_eq!(Duration::parse(" 2h ").unwrap(), Duration::from_secs(7200));
399
400 assert_eq!(Duration::parse("30 s").unwrap(), Duration::from_secs(30));
402 assert_eq!(
403 Duration::parse("500 ms").unwrap(),
404 Duration::from_millis(500)
405 );
406 assert_eq!(Duration::parse("2 h").unwrap(), Duration::from_secs(7200));
407 assert_eq!(Duration::parse("5 m").unwrap(), Duration::from_secs(300));
408 }
409
410 #[test]
411 fn test_duration_parse_error_cases() {
412 assert!(Duration::parse("invalid").is_err());
414 assert!(Duration::parse("abc123ms").is_err());
415 assert!(Duration::parse("12.5s").is_err()); assert!(Duration::parse("10x").is_err());
419 assert!(Duration::parse("30days").is_err());
420 assert!(Duration::parse("5y").is_err());
421
422 assert!(Duration::parse("5minutes").is_err());
424 assert!(Duration::parse("10seconds").is_err());
425 assert!(Duration::parse("2hours").is_err());
426 assert!(Duration::parse("500millis").is_err());
427 assert!(Duration::parse("30sec").is_err());
428 assert!(Duration::parse("5min").is_err());
429 assert!(Duration::parse("2hr").is_err());
430
431 assert!(Duration::parse("60").is_err());
433 assert!(Duration::parse("0").is_err());
434 assert!(Duration::parse("3600").is_err());
435 assert!(Duration::parse("1").is_err());
436
437 assert!(Duration::parse("").is_err());
439 assert!(Duration::parse(" ").is_err());
440
441 assert!(Duration::parse("-5s").is_err());
443 assert!(Duration::parse("-100ms").is_err());
444
445 assert!(Duration::parse("30S").is_err());
447 assert!(Duration::parse("500MS").is_err());
448 assert!(Duration::parse("2H").is_err());
449 }
450
451 #[test]
452 fn test_duration_parse_large_values() {
453 assert_eq!(
455 Duration::parse("999999999ms").unwrap(),
456 Duration::from_millis(999999999)
457 );
458 assert_eq!(
459 Duration::parse("99999999s").unwrap(),
460 Duration::from_secs(99999999)
461 );
462 }
463
464 #[test]
465 fn test_duration_parse_overflow_cases() {
466 let max_safe_hours = u64::MAX / 3600;
468 let overflow_hours = max_safe_hours + 1;
469 assert!(Duration::parse(&format!("{max_safe_hours}h")).is_ok());
470 match Duration::parse(&format!("{overflow_hours}h")) {
471 Err(msg) => assert!(msg.contains("too large (would overflow)")),
472 Ok(_) => panic!("Expected overflow error for large hours value"),
473 }
474 match Duration::parse(&format!("{}h", u64::MAX)) {
475 Err(msg) => assert!(msg.contains("too large (would overflow)")),
476 Ok(_) => panic!("Expected overflow error for u64::MAX hours"),
477 }
478
479 let max_safe_minutes = u64::MAX / 60;
481 let overflow_minutes = max_safe_minutes + 1;
482 assert!(Duration::parse(&format!("{max_safe_minutes}m")).is_ok());
483 match Duration::parse(&format!("{overflow_minutes}m")) {
484 Err(msg) => assert!(msg.contains("too large (would overflow)")),
485 Ok(_) => panic!("Expected overflow error for large minutes value"),
486 }
487 match Duration::parse(&format!("{}m", u64::MAX)) {
488 Err(msg) => assert!(msg.contains("too large (would overflow)")),
489 Ok(_) => panic!("Expected overflow error for u64::MAX minutes"),
490 }
491 }
492}