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 > Duration::MAX.as_nanos() {
98 return Duration::MAX;
99 }
100
101 let secs = (ns / NANOS_PER_SEC) as u64;
103 let nanos = (ns % NANOS_PER_SEC) as u32;
104 Duration::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(Duration::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(Duration::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(Duration::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(Duration::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 SystemTime::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
219 #[test]
220 fn test_epoch() {
221 let time = SystemTime::UNIX_EPOCH;
222 assert_eq!(time.epoch(), Duration::from_secs(0));
223
224 let time = SystemTime::UNIX_EPOCH + Duration::from_secs(1) + Duration::from_millis(1);
225 assert_eq!(time.epoch(), Duration::from_millis(1_001));
226 }
227
228 #[test]
229 #[should_panic(expected = "failed to get epoch time")]
230 fn test_epoch_panics() {
231 let time = SystemTime::UNIX_EPOCH - Duration::from_secs(1);
232 time.epoch();
233 }
234
235 #[test]
236 fn test_epoch_millis() {
237 let time = SystemTime::UNIX_EPOCH;
238 assert_eq!(time.epoch_millis(), 0);
239
240 let time = SystemTime::UNIX_EPOCH + Duration::from_secs(1) + Duration::from_millis(1);
241 assert_eq!(time.epoch_millis(), 1_001);
242
243 let time = SystemTime::UNIX_EPOCH + Duration::from_secs(1) + Duration::from_nanos(999_999);
245 assert_eq!(time.epoch_millis(), 1_000);
246
247 let time = SystemTime::UNIX_EPOCH + Duration::from_secs(300);
249 assert_eq!(time.epoch_millis(), 300_000);
250 }
251
252 #[test]
253 #[should_panic(expected = "failed to get epoch time")]
254 fn test_epoch_millis_panics() {
255 let time = SystemTime::UNIX_EPOCH - Duration::from_secs(1);
256 time.epoch_millis();
257 }
258
259 #[test]
260 fn test_from_nanos_saturating() {
261 assert_eq!(Duration::from_nanos_saturating(0), Duration::new(0, 0));
263 assert_eq!(
264 Duration::from_nanos_saturating(NANOS_PER_SEC - 1),
265 Duration::new(0, (NANOS_PER_SEC - 1) as u32)
266 );
267 assert_eq!(
268 Duration::from_nanos_saturating(NANOS_PER_SEC + 1),
269 Duration::new(1, 1)
270 );
271
272 let std = Duration::from_nanos(u64::MAX);
274 let beyond_std = Duration::from_nanos_saturating(u64::MAX as u128 + 1);
275 assert!(beyond_std > std);
276
277 assert_eq!(
279 Duration::from_nanos_saturating(Duration::MAX.as_nanos()),
280 Duration::MAX
281 );
282
283 assert_eq!(
285 Duration::from_nanos_saturating(Duration::MAX.as_nanos() + 1),
286 Duration::MAX
287 );
288 assert_eq!(Duration::from_nanos_saturating(u128::MAX), Duration::MAX);
289 }
290
291 #[test]
292 fn test_add_jittered() {
293 let mut rng = rand::thread_rng();
294 let time = SystemTime::UNIX_EPOCH + Duration::from_secs(1);
295 let jitter = Duration::from_secs(2);
296
297 let (mut below, mut above) = (false, false);
299 let avg = time + jitter;
300 for _ in 0..100 {
301 let new_time = time.add_jittered(&mut rng, jitter);
302
303 below |= new_time < avg;
305 above |= new_time > avg;
306
307 assert!(new_time >= time);
309 assert!(new_time <= time + (jitter * 2));
310 }
311 assert!(below && above);
312 }
313
314 #[test]
315 fn check_duration_limit() {
316 let result = SystemTime::limit()
318 .checked_add(SYSTEM_TIME_PRECISION - Duration::from_nanos(1))
319 .expect("addition within precision should round down");
320 assert_eq!(result, SystemTime::limit(), "unexpected precision");
321
322 let result = SystemTime::limit().checked_add(SYSTEM_TIME_PRECISION);
324 assert!(result.is_none(), "able to exceed max duration");
325 }
326
327 #[test]
328 fn system_time_saturating_add() {
329 let max = SystemTime::limit();
330 assert_eq!(max.saturating_add(Duration::from_nanos(1)), max);
331 assert_eq!(max.saturating_add(Duration::from_secs(1)), max);
332 }
333
334 #[test]
335 fn test_duration_parse_milliseconds() {
336 assert_eq!(
337 Duration::parse("500ms").unwrap(),
338 Duration::from_millis(500)
339 );
340 assert_eq!(Duration::parse("0ms").unwrap(), Duration::from_millis(0));
341 assert_eq!(Duration::parse("1ms").unwrap(), Duration::from_millis(1));
342 assert_eq!(
343 Duration::parse("1000ms").unwrap(),
344 Duration::from_millis(1000)
345 );
346 assert_eq!(
347 Duration::parse("250ms").unwrap(),
348 Duration::from_millis(250)
349 );
350 }
351
352 #[test]
353 fn test_duration_parse_seconds() {
354 assert_eq!(Duration::parse("30s").unwrap(), Duration::from_secs(30));
355 assert_eq!(Duration::parse("0s").unwrap(), Duration::from_secs(0));
356 assert_eq!(Duration::parse("1s").unwrap(), Duration::from_secs(1));
357 assert_eq!(Duration::parse("45s").unwrap(), Duration::from_secs(45));
358 assert_eq!(Duration::parse("60s").unwrap(), Duration::from_secs(60));
359 assert_eq!(Duration::parse("3600s").unwrap(), Duration::from_secs(3600));
360 }
361
362 #[test]
363 fn test_duration_parse_minutes() {
364 assert_eq!(Duration::parse("5m").unwrap(), Duration::from_secs(300));
365 assert_eq!(Duration::parse("1m").unwrap(), Duration::from_secs(60));
366 assert_eq!(Duration::parse("0m").unwrap(), Duration::from_secs(0));
367 assert_eq!(Duration::parse("10m").unwrap(), Duration::from_secs(600));
368 assert_eq!(Duration::parse("15m").unwrap(), Duration::from_secs(900));
369 assert_eq!(Duration::parse("30m").unwrap(), Duration::from_secs(1800));
370 assert_eq!(Duration::parse("60m").unwrap(), Duration::from_secs(3600));
371 }
372
373 #[test]
374 fn test_duration_parse_hours() {
375 assert_eq!(Duration::parse("2h").unwrap(), Duration::from_secs(7200));
376 assert_eq!(Duration::parse("1h").unwrap(), Duration::from_secs(3600));
377 assert_eq!(Duration::parse("0h").unwrap(), Duration::from_secs(0));
378 assert_eq!(Duration::parse("3h").unwrap(), Duration::from_secs(10800));
379 assert_eq!(Duration::parse("4h").unwrap(), Duration::from_secs(14400));
380 assert_eq!(Duration::parse("12h").unwrap(), Duration::from_secs(43200));
381 assert_eq!(Duration::parse("24h").unwrap(), Duration::from_secs(86400));
382 assert_eq!(
383 Duration::parse("168h").unwrap(),
384 Duration::from_secs(604800)
385 );
386 }
388
389 #[test]
390 fn test_duration_parse_whitespace() {
391 assert_eq!(Duration::parse(" 30s ").unwrap(), Duration::from_secs(30));
393 assert_eq!(
394 Duration::parse("\t500ms\n").unwrap(),
395 Duration::from_millis(500)
396 );
397 assert_eq!(Duration::parse(" 2h ").unwrap(), Duration::from_secs(7200));
398
399 assert_eq!(Duration::parse("30 s").unwrap(), Duration::from_secs(30));
401 assert_eq!(
402 Duration::parse("500 ms").unwrap(),
403 Duration::from_millis(500)
404 );
405 assert_eq!(Duration::parse("2 h").unwrap(), Duration::from_secs(7200));
406 assert_eq!(Duration::parse("5 m").unwrap(), Duration::from_secs(300));
407 }
408
409 #[test]
410 fn test_duration_parse_error_cases() {
411 assert!(Duration::parse("invalid").is_err());
413 assert!(Duration::parse("abc123ms").is_err());
414 assert!(Duration::parse("12.5s").is_err()); assert!(Duration::parse("10x").is_err());
418 assert!(Duration::parse("30days").is_err());
419 assert!(Duration::parse("5y").is_err());
420
421 assert!(Duration::parse("5minutes").is_err());
423 assert!(Duration::parse("10seconds").is_err());
424 assert!(Duration::parse("2hours").is_err());
425 assert!(Duration::parse("500millis").is_err());
426 assert!(Duration::parse("30sec").is_err());
427 assert!(Duration::parse("5min").is_err());
428 assert!(Duration::parse("2hr").is_err());
429
430 assert!(Duration::parse("60").is_err());
432 assert!(Duration::parse("0").is_err());
433 assert!(Duration::parse("3600").is_err());
434 assert!(Duration::parse("1").is_err());
435
436 assert!(Duration::parse("").is_err());
438 assert!(Duration::parse(" ").is_err());
439
440 assert!(Duration::parse("-5s").is_err());
442 assert!(Duration::parse("-100ms").is_err());
443
444 assert!(Duration::parse("30S").is_err());
446 assert!(Duration::parse("500MS").is_err());
447 assert!(Duration::parse("2H").is_err());
448 }
449
450 #[test]
451 fn test_duration_parse_large_values() {
452 assert_eq!(
454 Duration::parse("999999999ms").unwrap(),
455 Duration::from_millis(999999999)
456 );
457 assert_eq!(
458 Duration::parse("99999999s").unwrap(),
459 Duration::from_secs(99999999)
460 );
461 }
462
463 #[test]
464 fn test_duration_parse_overflow_cases() {
465 let max_safe_hours = u64::MAX / 3600;
467 let overflow_hours = max_safe_hours + 1;
468 assert!(Duration::parse(&format!("{max_safe_hours}h")).is_ok());
469 match Duration::parse(&format!("{overflow_hours}h")) {
470 Err(msg) => assert!(msg.contains("too large (would overflow)")),
471 Ok(_) => panic!("Expected overflow error for large hours value"),
472 }
473 match Duration::parse(&format!("{}h", u64::MAX)) {
474 Err(msg) => assert!(msg.contains("too large (would overflow)")),
475 Ok(_) => panic!("Expected overflow error for u64::MAX hours"),
476 }
477
478 let max_safe_minutes = u64::MAX / 60;
480 let overflow_minutes = max_safe_minutes + 1;
481 assert!(Duration::parse(&format!("{max_safe_minutes}m")).is_ok());
482 match Duration::parse(&format!("{overflow_minutes}m")) {
483 Err(msg) => assert!(msg.contains("too large (would overflow)")),
484 Ok(_) => panic!("Expected overflow error for large minutes value"),
485 }
486 match Duration::parse(&format!("{}m", u64::MAX)) {
487 Err(msg) => assert!(msg.contains("too large (would overflow)")),
488 Ok(_) => panic!("Expected overflow error for u64::MAX minutes"),
489 }
490 }
491}