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