blob: 7546d1b367c5e2907da0e0efac46a9964c32fb0b [file] [log] [blame]
Andrew Walbran12f61402020-10-14 11:10:53 +01001use std::{cmp, fmt, ops};
2use std::time::Duration;
3use std::convert::From;
Joel Galenson4727c112021-04-02 12:22:24 -07004use libc::{timespec, timeval};
5#[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848
Andrew Walbran12f61402020-10-14 11:10:53 +01006pub use libc::{time_t, suseconds_t};
7
8pub trait TimeValLike: Sized {
9 #[inline]
10 fn zero() -> Self {
11 Self::seconds(0)
12 }
13
14 #[inline]
15 fn hours(hours: i64) -> Self {
16 let secs = hours.checked_mul(SECS_PER_HOUR)
17 .expect("TimeValLike::hours ouf of bounds");
18 Self::seconds(secs)
19 }
20
21 #[inline]
22 fn minutes(minutes: i64) -> Self {
23 let secs = minutes.checked_mul(SECS_PER_MINUTE)
24 .expect("TimeValLike::minutes out of bounds");
25 Self::seconds(secs)
26 }
27
28 fn seconds(seconds: i64) -> Self;
29 fn milliseconds(milliseconds: i64) -> Self;
30 fn microseconds(microseconds: i64) -> Self;
31 fn nanoseconds(nanoseconds: i64) -> Self;
32
33 #[inline]
34 fn num_hours(&self) -> i64 {
35 self.num_seconds() / 3600
36 }
37
38 #[inline]
39 fn num_minutes(&self) -> i64 {
40 self.num_seconds() / 60
41 }
42
43 fn num_seconds(&self) -> i64;
44 fn num_milliseconds(&self) -> i64;
45 fn num_microseconds(&self) -> i64;
46 fn num_nanoseconds(&self) -> i64;
47}
48
49#[repr(C)]
50#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
51pub struct TimeSpec(timespec);
52
53const NANOS_PER_SEC: i64 = 1_000_000_000;
54const SECS_PER_MINUTE: i64 = 60;
55const SECS_PER_HOUR: i64 = 3600;
56
57#[cfg(target_pointer_width = "64")]
58const TS_MAX_SECONDS: i64 = (::std::i64::MAX / NANOS_PER_SEC) - 1;
59
60#[cfg(target_pointer_width = "32")]
61const TS_MAX_SECONDS: i64 = ::std::isize::MAX as i64;
62
63const TS_MIN_SECONDS: i64 = -TS_MAX_SECONDS;
64
Joel Galenson4727c112021-04-02 12:22:24 -070065// x32 compatibility
66// See https://sourceware.org/bugzilla/show_bug.cgi?id=16437
67#[cfg(all(target_arch = "x86_64", target_pointer_width = "32"))]
68type timespec_tv_nsec_t = i64;
69#[cfg(not(all(target_arch = "x86_64", target_pointer_width = "32")))]
70type timespec_tv_nsec_t = libc::c_long;
71
Andrew Walbran12f61402020-10-14 11:10:53 +010072impl From<timespec> for TimeSpec {
73 fn from(ts: timespec) -> Self {
74 Self(ts)
75 }
76}
77
78impl From<Duration> for TimeSpec {
79 fn from(duration: Duration) -> Self {
Joel Galenson4727c112021-04-02 12:22:24 -070080 #[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848
Andrew Walbran12f61402020-10-14 11:10:53 +010081 TimeSpec(timespec {
82 tv_sec: duration.as_secs() as time_t,
Joel Galenson4727c112021-04-02 12:22:24 -070083 tv_nsec: duration.subsec_nanos() as timespec_tv_nsec_t
Andrew Walbran12f61402020-10-14 11:10:53 +010084 })
85 }
86}
87
88impl From<TimeSpec> for Duration {
89 fn from(timespec: TimeSpec) -> Self {
90 Duration::new(timespec.0.tv_sec as u64, timespec.0.tv_nsec as u32)
91 }
92}
93
94impl AsRef<timespec> for TimeSpec {
95 fn as_ref(&self) -> &timespec {
96 &self.0
97 }
98}
99
100impl AsMut<timespec> for TimeSpec {
101 fn as_mut(&mut self) -> &mut timespec {
102 &mut self.0
103 }
104}
105
106impl Ord for TimeSpec {
107 // The implementation of cmp is simplified by assuming that the struct is
108 // normalized. That is, tv_nsec must always be within [0, 1_000_000_000)
109 fn cmp(&self, other: &TimeSpec) -> cmp::Ordering {
110 if self.tv_sec() == other.tv_sec() {
111 self.tv_nsec().cmp(&other.tv_nsec())
112 } else {
113 self.tv_sec().cmp(&other.tv_sec())
114 }
115 }
116}
117
118impl PartialOrd for TimeSpec {
119 fn partial_cmp(&self, other: &TimeSpec) -> Option<cmp::Ordering> {
120 Some(self.cmp(other))
121 }
122}
123
124impl TimeValLike for TimeSpec {
125 #[inline]
126 fn seconds(seconds: i64) -> TimeSpec {
127 assert!(seconds >= TS_MIN_SECONDS && seconds <= TS_MAX_SECONDS,
128 "TimeSpec out of bounds; seconds={}", seconds);
Joel Galenson4727c112021-04-02 12:22:24 -0700129 #[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848
Andrew Walbran12f61402020-10-14 11:10:53 +0100130 TimeSpec(timespec {tv_sec: seconds as time_t, tv_nsec: 0 })
131 }
132
133 #[inline]
134 fn milliseconds(milliseconds: i64) -> TimeSpec {
135 let nanoseconds = milliseconds.checked_mul(1_000_000)
136 .expect("TimeSpec::milliseconds out of bounds");
137
138 TimeSpec::nanoseconds(nanoseconds)
139 }
140
141 /// Makes a new `TimeSpec` with given number of microseconds.
142 #[inline]
143 fn microseconds(microseconds: i64) -> TimeSpec {
144 let nanoseconds = microseconds.checked_mul(1_000)
145 .expect("TimeSpec::milliseconds out of bounds");
146
147 TimeSpec::nanoseconds(nanoseconds)
148 }
149
150 /// Makes a new `TimeSpec` with given number of nanoseconds.
151 #[inline]
152 fn nanoseconds(nanoseconds: i64) -> TimeSpec {
153 let (secs, nanos) = div_mod_floor_64(nanoseconds, NANOS_PER_SEC);
154 assert!(secs >= TS_MIN_SECONDS && secs <= TS_MAX_SECONDS,
155 "TimeSpec out of bounds");
Joel Galenson4727c112021-04-02 12:22:24 -0700156 #[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848
Andrew Walbran12f61402020-10-14 11:10:53 +0100157 TimeSpec(timespec {tv_sec: secs as time_t,
Joel Galenson4727c112021-04-02 12:22:24 -0700158 tv_nsec: nanos as timespec_tv_nsec_t })
Andrew Walbran12f61402020-10-14 11:10:53 +0100159 }
160
161 fn num_seconds(&self) -> i64 {
162 if self.tv_sec() < 0 && self.tv_nsec() > 0 {
163 (self.tv_sec() + 1) as i64
164 } else {
165 self.tv_sec() as i64
166 }
167 }
168
169 fn num_milliseconds(&self) -> i64 {
170 self.num_nanoseconds() / 1_000_000
171 }
172
173 fn num_microseconds(&self) -> i64 {
174 self.num_nanoseconds() / 1_000_000_000
175 }
176
177 fn num_nanoseconds(&self) -> i64 {
178 let secs = self.num_seconds() * 1_000_000_000;
179 let nsec = self.nanos_mod_sec();
180 secs + nsec as i64
181 }
182}
183
184impl TimeSpec {
Joel Galenson4727c112021-04-02 12:22:24 -0700185 fn nanos_mod_sec(&self) -> timespec_tv_nsec_t {
Andrew Walbran12f61402020-10-14 11:10:53 +0100186 if self.tv_sec() < 0 && self.tv_nsec() > 0 {
Joel Galenson4727c112021-04-02 12:22:24 -0700187 self.tv_nsec() - NANOS_PER_SEC as timespec_tv_nsec_t
Andrew Walbran12f61402020-10-14 11:10:53 +0100188 } else {
189 self.tv_nsec()
190 }
191 }
192
Joel Galenson4727c112021-04-02 12:22:24 -0700193 #[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848
Andrew Walbran12f61402020-10-14 11:10:53 +0100194 pub fn tv_sec(&self) -> time_t {
195 self.0.tv_sec
196 }
197
Joel Galenson4727c112021-04-02 12:22:24 -0700198 pub fn tv_nsec(&self) -> timespec_tv_nsec_t {
Andrew Walbran12f61402020-10-14 11:10:53 +0100199 self.0.tv_nsec
200 }
201}
202
203impl ops::Neg for TimeSpec {
204 type Output = TimeSpec;
205
206 fn neg(self) -> TimeSpec {
207 TimeSpec::nanoseconds(-self.num_nanoseconds())
208 }
209}
210
211impl ops::Add for TimeSpec {
212 type Output = TimeSpec;
213
214 fn add(self, rhs: TimeSpec) -> TimeSpec {
215 TimeSpec::nanoseconds(
216 self.num_nanoseconds() + rhs.num_nanoseconds())
217 }
218}
219
220impl ops::Sub for TimeSpec {
221 type Output = TimeSpec;
222
223 fn sub(self, rhs: TimeSpec) -> TimeSpec {
224 TimeSpec::nanoseconds(
225 self.num_nanoseconds() - rhs.num_nanoseconds())
226 }
227}
228
229impl ops::Mul<i32> for TimeSpec {
230 type Output = TimeSpec;
231
232 fn mul(self, rhs: i32) -> TimeSpec {
233 let usec = self.num_nanoseconds().checked_mul(i64::from(rhs))
234 .expect("TimeSpec multiply out of bounds");
235
236 TimeSpec::nanoseconds(usec)
237 }
238}
239
240impl ops::Div<i32> for TimeSpec {
241 type Output = TimeSpec;
242
243 fn div(self, rhs: i32) -> TimeSpec {
244 let usec = self.num_nanoseconds() / i64::from(rhs);
245 TimeSpec::nanoseconds(usec)
246 }
247}
248
249impl fmt::Display for TimeSpec {
250 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
251 let (abs, sign) = if self.tv_sec() < 0 {
252 (-*self, "-")
253 } else {
254 (*self, "")
255 };
256
257 let sec = abs.tv_sec();
258
259 write!(f, "{}", sign)?;
260
261 if abs.tv_nsec() == 0 {
262 if abs.tv_sec() == 1 {
263 write!(f, "{} second", sec)?;
264 } else {
265 write!(f, "{} seconds", sec)?;
266 }
267 } else if abs.tv_nsec() % 1_000_000 == 0 {
268 write!(f, "{}.{:03} seconds", sec, abs.tv_nsec() / 1_000_000)?;
269 } else if abs.tv_nsec() % 1_000 == 0 {
270 write!(f, "{}.{:06} seconds", sec, abs.tv_nsec() / 1_000)?;
271 } else {
272 write!(f, "{}.{:09} seconds", sec, abs.tv_nsec())?;
273 }
274
275 Ok(())
276 }
277}
278
279
280
281#[repr(transparent)]
282#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
283pub struct TimeVal(timeval);
284
285const MICROS_PER_SEC: i64 = 1_000_000;
286
287#[cfg(target_pointer_width = "64")]
288const TV_MAX_SECONDS: i64 = (::std::i64::MAX / MICROS_PER_SEC) - 1;
289
290#[cfg(target_pointer_width = "32")]
291const TV_MAX_SECONDS: i64 = ::std::isize::MAX as i64;
292
293const TV_MIN_SECONDS: i64 = -TV_MAX_SECONDS;
294
295impl AsRef<timeval> for TimeVal {
296 fn as_ref(&self) -> &timeval {
297 &self.0
298 }
299}
300
301impl AsMut<timeval> for TimeVal {
302 fn as_mut(&mut self) -> &mut timeval {
303 &mut self.0
304 }
305}
306
307impl Ord for TimeVal {
308 // The implementation of cmp is simplified by assuming that the struct is
309 // normalized. That is, tv_usec must always be within [0, 1_000_000)
310 fn cmp(&self, other: &TimeVal) -> cmp::Ordering {
311 if self.tv_sec() == other.tv_sec() {
312 self.tv_usec().cmp(&other.tv_usec())
313 } else {
314 self.tv_sec().cmp(&other.tv_sec())
315 }
316 }
317}
318
319impl PartialOrd for TimeVal {
320 fn partial_cmp(&self, other: &TimeVal) -> Option<cmp::Ordering> {
321 Some(self.cmp(other))
322 }
323}
324
325impl TimeValLike for TimeVal {
326 #[inline]
327 fn seconds(seconds: i64) -> TimeVal {
328 assert!(seconds >= TV_MIN_SECONDS && seconds <= TV_MAX_SECONDS,
329 "TimeVal out of bounds; seconds={}", seconds);
Joel Galenson4727c112021-04-02 12:22:24 -0700330 #[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848
Andrew Walbran12f61402020-10-14 11:10:53 +0100331 TimeVal(timeval {tv_sec: seconds as time_t, tv_usec: 0 })
332 }
333
334 #[inline]
335 fn milliseconds(milliseconds: i64) -> TimeVal {
336 let microseconds = milliseconds.checked_mul(1_000)
337 .expect("TimeVal::milliseconds out of bounds");
338
339 TimeVal::microseconds(microseconds)
340 }
341
342 /// Makes a new `TimeVal` with given number of microseconds.
343 #[inline]
344 fn microseconds(microseconds: i64) -> TimeVal {
345 let (secs, micros) = div_mod_floor_64(microseconds, MICROS_PER_SEC);
346 assert!(secs >= TV_MIN_SECONDS && secs <= TV_MAX_SECONDS,
347 "TimeVal out of bounds");
Joel Galenson4727c112021-04-02 12:22:24 -0700348 #[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848
Andrew Walbran12f61402020-10-14 11:10:53 +0100349 TimeVal(timeval {tv_sec: secs as time_t,
350 tv_usec: micros as suseconds_t })
351 }
352
353 /// Makes a new `TimeVal` with given number of nanoseconds. Some precision
354 /// will be lost
355 #[inline]
356 fn nanoseconds(nanoseconds: i64) -> TimeVal {
357 let microseconds = nanoseconds / 1000;
358 let (secs, micros) = div_mod_floor_64(microseconds, MICROS_PER_SEC);
359 assert!(secs >= TV_MIN_SECONDS && secs <= TV_MAX_SECONDS,
360 "TimeVal out of bounds");
Joel Galenson4727c112021-04-02 12:22:24 -0700361 #[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848
Andrew Walbran12f61402020-10-14 11:10:53 +0100362 TimeVal(timeval {tv_sec: secs as time_t,
363 tv_usec: micros as suseconds_t })
364 }
365
366 fn num_seconds(&self) -> i64 {
367 if self.tv_sec() < 0 && self.tv_usec() > 0 {
368 (self.tv_sec() + 1) as i64
369 } else {
370 self.tv_sec() as i64
371 }
372 }
373
374 fn num_milliseconds(&self) -> i64 {
375 self.num_microseconds() / 1_000
376 }
377
378 fn num_microseconds(&self) -> i64 {
379 let secs = self.num_seconds() * 1_000_000;
380 let usec = self.micros_mod_sec();
381 secs + usec as i64
382 }
383
384 fn num_nanoseconds(&self) -> i64 {
385 self.num_microseconds() * 1_000
386 }
387}
388
389impl TimeVal {
390 fn micros_mod_sec(&self) -> suseconds_t {
391 if self.tv_sec() < 0 && self.tv_usec() > 0 {
392 self.tv_usec() - MICROS_PER_SEC as suseconds_t
393 } else {
394 self.tv_usec()
395 }
396 }
397
Joel Galenson4727c112021-04-02 12:22:24 -0700398 #[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848
Andrew Walbran12f61402020-10-14 11:10:53 +0100399 pub fn tv_sec(&self) -> time_t {
400 self.0.tv_sec
401 }
402
403 pub fn tv_usec(&self) -> suseconds_t {
404 self.0.tv_usec
405 }
406}
407
408impl ops::Neg for TimeVal {
409 type Output = TimeVal;
410
411 fn neg(self) -> TimeVal {
412 TimeVal::microseconds(-self.num_microseconds())
413 }
414}
415
416impl ops::Add for TimeVal {
417 type Output = TimeVal;
418
419 fn add(self, rhs: TimeVal) -> TimeVal {
420 TimeVal::microseconds(
421 self.num_microseconds() + rhs.num_microseconds())
422 }
423}
424
425impl ops::Sub for TimeVal {
426 type Output = TimeVal;
427
428 fn sub(self, rhs: TimeVal) -> TimeVal {
429 TimeVal::microseconds(
430 self.num_microseconds() - rhs.num_microseconds())
431 }
432}
433
434impl ops::Mul<i32> for TimeVal {
435 type Output = TimeVal;
436
437 fn mul(self, rhs: i32) -> TimeVal {
438 let usec = self.num_microseconds().checked_mul(i64::from(rhs))
439 .expect("TimeVal multiply out of bounds");
440
441 TimeVal::microseconds(usec)
442 }
443}
444
445impl ops::Div<i32> for TimeVal {
446 type Output = TimeVal;
447
448 fn div(self, rhs: i32) -> TimeVal {
449 let usec = self.num_microseconds() / i64::from(rhs);
450 TimeVal::microseconds(usec)
451 }
452}
453
454impl fmt::Display for TimeVal {
455 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
456 let (abs, sign) = if self.tv_sec() < 0 {
457 (-*self, "-")
458 } else {
459 (*self, "")
460 };
461
462 let sec = abs.tv_sec();
463
464 write!(f, "{}", sign)?;
465
466 if abs.tv_usec() == 0 {
467 if abs.tv_sec() == 1 {
468 write!(f, "{} second", sec)?;
469 } else {
470 write!(f, "{} seconds", sec)?;
471 }
472 } else if abs.tv_usec() % 1000 == 0 {
473 write!(f, "{}.{:03} seconds", sec, abs.tv_usec() / 1000)?;
474 } else {
475 write!(f, "{}.{:06} seconds", sec, abs.tv_usec())?;
476 }
477
478 Ok(())
479 }
480}
481
482impl From<timeval> for TimeVal {
483 fn from(tv: timeval) -> Self {
484 TimeVal(tv)
485 }
486}
487
488#[inline]
489fn div_mod_floor_64(this: i64, other: i64) -> (i64, i64) {
490 (div_floor_64(this, other), mod_floor_64(this, other))
491}
492
493#[inline]
494fn div_floor_64(this: i64, other: i64) -> i64 {
495 match div_rem_64(this, other) {
496 (d, r) if (r > 0 && other < 0)
497 || (r < 0 && other > 0) => d - 1,
498 (d, _) => d,
499 }
500}
501
502#[inline]
503fn mod_floor_64(this: i64, other: i64) -> i64 {
504 match this % other {
505 r if (r > 0 && other < 0)
506 || (r < 0 && other > 0) => r + other,
507 r => r,
508 }
509}
510
511#[inline]
512fn div_rem_64(this: i64, other: i64) -> (i64, i64) {
513 (this / other, this % other)
514}
515
516#[cfg(test)]
517mod test {
518 use super::{TimeSpec, TimeVal, TimeValLike};
519 use std::time::Duration;
520
521 #[test]
522 pub fn test_timespec() {
523 assert!(TimeSpec::seconds(1) != TimeSpec::zero());
524 assert_eq!(TimeSpec::seconds(1) + TimeSpec::seconds(2),
525 TimeSpec::seconds(3));
526 assert_eq!(TimeSpec::minutes(3) + TimeSpec::seconds(2),
527 TimeSpec::seconds(182));
528 }
529
530 #[test]
531 pub fn test_timespec_from() {
532 let duration = Duration::new(123, 123_456_789);
533 let timespec = TimeSpec::nanoseconds(123_123_456_789);
534
535 assert_eq!(TimeSpec::from(duration), timespec);
536 assert_eq!(Duration::from(timespec), duration);
537 }
538
539 #[test]
540 pub fn test_timespec_neg() {
541 let a = TimeSpec::seconds(1) + TimeSpec::nanoseconds(123);
542 let b = TimeSpec::seconds(-1) + TimeSpec::nanoseconds(-123);
543
544 assert_eq!(a, -b);
545 }
546
547 #[test]
548 pub fn test_timespec_ord() {
549 assert!(TimeSpec::seconds(1) == TimeSpec::nanoseconds(1_000_000_000));
550 assert!(TimeSpec::seconds(1) < TimeSpec::nanoseconds(1_000_000_001));
551 assert!(TimeSpec::seconds(1) > TimeSpec::nanoseconds(999_999_999));
552 assert!(TimeSpec::seconds(-1) < TimeSpec::nanoseconds(-999_999_999));
553 assert!(TimeSpec::seconds(-1) > TimeSpec::nanoseconds(-1_000_000_001));
554 }
555
556 #[test]
557 pub fn test_timespec_fmt() {
558 assert_eq!(TimeSpec::zero().to_string(), "0 seconds");
559 assert_eq!(TimeSpec::seconds(42).to_string(), "42 seconds");
560 assert_eq!(TimeSpec::milliseconds(42).to_string(), "0.042 seconds");
561 assert_eq!(TimeSpec::microseconds(42).to_string(), "0.000042 seconds");
562 assert_eq!(TimeSpec::nanoseconds(42).to_string(), "0.000000042 seconds");
563 assert_eq!(TimeSpec::seconds(-86401).to_string(), "-86401 seconds");
564 }
565
566 #[test]
567 pub fn test_timeval() {
568 assert!(TimeVal::seconds(1) != TimeVal::zero());
569 assert_eq!(TimeVal::seconds(1) + TimeVal::seconds(2),
570 TimeVal::seconds(3));
571 assert_eq!(TimeVal::minutes(3) + TimeVal::seconds(2),
572 TimeVal::seconds(182));
573 }
574
575 #[test]
576 pub fn test_timeval_ord() {
577 assert!(TimeVal::seconds(1) == TimeVal::microseconds(1_000_000));
578 assert!(TimeVal::seconds(1) < TimeVal::microseconds(1_000_001));
579 assert!(TimeVal::seconds(1) > TimeVal::microseconds(999_999));
580 assert!(TimeVal::seconds(-1) < TimeVal::microseconds(-999_999));
581 assert!(TimeVal::seconds(-1) > TimeVal::microseconds(-1_000_001));
582 }
583
584 #[test]
585 pub fn test_timeval_neg() {
586 let a = TimeVal::seconds(1) + TimeVal::microseconds(123);
587 let b = TimeVal::seconds(-1) + TimeVal::microseconds(-123);
588
589 assert_eq!(a, -b);
590 }
591
592 #[test]
593 pub fn test_timeval_fmt() {
594 assert_eq!(TimeVal::zero().to_string(), "0 seconds");
595 assert_eq!(TimeVal::seconds(42).to_string(), "42 seconds");
596 assert_eq!(TimeVal::milliseconds(42).to_string(), "0.042 seconds");
597 assert_eq!(TimeVal::microseconds(42).to_string(), "0.000042 seconds");
598 assert_eq!(TimeVal::nanoseconds(1402).to_string(), "0.000001 seconds");
599 assert_eq!(TimeVal::seconds(-86401).to_string(), "-86401 seconds");
600 }
601}