1mod source;
5
6use std::{fmt, time::{SystemTime, UNIX_EPOCH, Duration}, ops::{Add, Sub}};
7
8pub use source::TimeSource;
9
10const NANO: u64 = 1_000_000_000;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
14#[cfg_attr(feature = "minicbor", derive(minicbor::Encode, minicbor::Decode), cbor(transparent))]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(transparent))]
16pub struct Time(u64);
17
18impl Time {
19 pub const MIN: Self = Time(0);
21
22 pub const MAX: Self = Time(u64::MAX);
24
25 pub fn now() -> Self {
31 Self::try_now().expect("SystemTime in range")
32 }
33
34 pub fn try_now() -> Result<Self, OutOfRange> {
38 let t = SystemTime::now();
39 let Ok(d) = t.duration_since(UNIX_EPOCH) else {
40 return Err(OutOfRange(()))
41 };
42 Self::try_from_sn(d.as_secs(), d.subsec_nanos())
43 }
44
45 #[cfg(feature = "coarse")]
53 pub fn coarse() -> Self {
54 Self::try_coarse().expect("valid time")
55 }
56
57 #[cfg(feature = "coarse")]
63 pub fn try_coarse() -> Result<Self, OutOfRange> {
64 use rustix::time::{clock_gettime, ClockId};
65 let t = clock_gettime(ClockId::RealtimeCoarse);
66 if t.tv_sec < 0 || t.tv_nsec < 0 {
67 return Err(OutOfRange(()))
68 }
69 Self::try_from_sn(t.tv_sec as u64, t.tv_nsec as u64)
70 }
71
72 pub fn from_seconds(s: u64) -> Self {
78 Self::try_from_seconds(s).expect("seconds in range")
79 }
80
81 pub fn try_from_seconds(s: u64) -> Result<Self, OutOfRange> {
85 Self::try_from_sn(s, 0u64)
86 }
87
88 pub const fn from_nanos(n: u64) -> Self {
90 Self(n)
91 }
92
93 pub const fn seconds(self) -> u64 {
95 self.0 / NANO
96 }
97
98 pub const fn nanos(self) -> u64 {
100 self.0
101 }
102
103 pub const fn second_nanos(self) -> u64 {
105 self.nanos() - self.seconds() * NANO
106 }
107
108 pub fn try_add(self, t: Time) -> Result<Self, OutOfRange> {
112 let n = self.0.checked_add(t.0).ok_or(OutOfRange(()))?;
113 Ok(Self(n))
114 }
115
116 pub fn try_sub(self, t: Time) -> Result<Self, OutOfRange> {
120 let n = self.0.checked_sub(t.0).ok_or(OutOfRange(()))?;
121 Ok(Self(n))
122 }
123
124 pub fn add_nanos(self, n: u64) -> Self {
132 self.add_nanos_checked(n).expect("no overflow")
133 }
134
135 pub fn add_seconds(self, s: u64) -> Self {
143 self.add_seconds_checked(s).expect("no overflow")
144 }
145
146 pub fn add_minutes(self, m: u64) -> Self {
154 self.add_minutes_checked(m).expect("no overflow")
155 }
156
157 pub fn add_hours(self, h: u64) -> Self {
165 self.add_hours_checked(h).expect("no overflow")
166 }
167
168 pub fn add_days(self, d: u64) -> Self {
176 self.add_days_checked(d).expect("no overflow")
177 }
178
179 pub fn add_nanos_checked(self, n: u64) -> Result<Self, OutOfRange> {
183 let n = self.0.checked_add(n).ok_or(OutOfRange::new())?;
184 Ok(Self(n))
185 }
186
187 pub fn add_seconds_checked(self, s: u64) -> Result<Self, OutOfRange> {
191 self.add_nanos_checked(s.checked_mul(NANO).ok_or(OutOfRange::new())?)
192 }
193
194 pub fn add_minutes_checked(self, m: u64) -> Result<Self, OutOfRange> {
198 self.add_seconds_checked(m.checked_mul(60).ok_or(OutOfRange::new())?)
199 }
200
201 pub fn add_hours_checked(self, h: u64) -> Result<Self, OutOfRange> {
205 self.add_seconds_checked(h.checked_mul(3600).ok_or(OutOfRange::new())?)
206 }
207
208 pub fn add_days_checked(self, d: u64) -> Result<Self, OutOfRange> {
212 self.add_seconds_checked(d.checked_mul(24 * 3600).ok_or(OutOfRange::new())?)
213 }
214
215 pub fn sub_nanos(self, n: u64) -> Self {
223 self.sub_nanos_checked(n).expect("no underflow")
224 }
225
226 pub fn sub_seconds(self, s: u64) -> Self {
234 self.sub_seconds_checked(s).expect("no underflow")
235 }
236
237 pub fn sub_minutes(self, m: u64) -> Self {
245 self.sub_minutes_checked(m).expect("no underflow")
246 }
247
248 pub fn sub_hours(self, h: u64) -> Self {
256 self.sub_hours_checked(h).expect("no underflow")
257 }
258
259 pub fn sub_days(self, d: u64) -> Self {
267 self.sub_days_checked(d).expect("no underflow")
268 }
269
270 pub fn sub_nanos_checked(self, n: u64) -> Result<Self, OutOfRange> {
274 let n = self.0.checked_sub(n).ok_or(OutOfRange::new())?;
275 Ok(Self(n))
276 }
277
278 pub fn sub_seconds_checked(self, s: u64) -> Result<Self, OutOfRange> {
282 self.sub_nanos_checked(s.checked_mul(NANO).ok_or(OutOfRange::new())?)
283 }
284
285 pub fn sub_minutes_checked(self, m: u64) -> Result<Self, OutOfRange> {
289 self.sub_seconds_checked(m.checked_mul(60).ok_or(OutOfRange::new())?)
290 }
291
292 pub fn sub_hours_checked(self, h: u64) -> Result<Self, OutOfRange> {
296 self.sub_seconds_checked(h.checked_mul(3600).ok_or(OutOfRange::new())?)
297 }
298
299 pub fn sub_days_checked(self, d: u64) -> Result<Self, OutOfRange> {
303 self.sub_seconds_checked(d.checked_mul(24 * 3600).ok_or(OutOfRange::new())?)
304 }
305
306 pub const fn to_duration(self) -> Duration {
308 let s = self.seconds();
309 let n = self.second_nanos();
310 Duration::new(s, n as u32)
311 }
312
313 pub fn duration_since(self, earlier: Self) -> Option<Duration> {
317 self.to_duration().checked_sub(earlier.to_duration())
318 }
319
320 pub fn as_u64(self) -> u64 {
322 self.0
323 }
324
325 #[cfg(feature = "utc")]
329 pub fn to_utc_string(self) -> Option<String> {
330 let s = self.seconds();
331 let n = self.second_nanos();
332 if let Ok(utc) = tz::UtcDateTime::from_timespec(s as i64, n as u32) {
333 Some(utc.to_string())
334 } else {
335 None
336 }
337 }
338
339 fn try_from_sn<S: Into<u64>, N: Into<u64>>(s: S, n: N) -> Result<Self, OutOfRange> {
341 let n = s.into()
342 .checked_mul(NANO)
343 .and_then(|x| x.checked_add(n.into()))
344 .ok_or(OutOfRange::new())?;
345 Ok(Self(n))
346 }
347}
348
349impl From<Time> for u64 {
350 fn from(value: Time) -> Self {
351 value.0
352 }
353}
354
355impl From<u64> for Time {
356 fn from(n: u64) -> Self {
357 Self(n)
358 }
359}
360
361#[derive(Debug)]
363pub struct OutOfRange(());
364
365impl OutOfRange {
366 fn new() -> Self {
367 Self(())
368 }
369}
370
371impl fmt::Display for OutOfRange {
372 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
373 f.write_str("out of range")
374 }
375}
376
377impl std::error::Error for OutOfRange {}
378
379impl Add<Duration> for Time {
380 type Output = Self;
381
382 fn add(self, d: Duration) -> Self::Output {
388 self.add_seconds(d.as_secs())
389 .add_nanos(d.subsec_nanos().into())
390 }
391}
392
393impl Add<Time> for Time {
394 type Output = Self;
395
396 fn add(self, t: Time) -> Self::Output {
402 self.add_nanos(t.nanos())
403 }
404}
405
406impl Sub<Duration> for Time {
407 type Output = Self;
408
409 fn sub(self, d: Duration) -> Self::Output {
415 self.sub_seconds(d.as_secs())
416 .sub_nanos(d.subsec_nanos().into())
417 }
418}
419
420impl Sub<Time> for Time {
421 type Output = Self;
422
423 fn sub(self, t: Time) -> Self::Output {
429 self.sub_nanos(t.nanos())
430 }
431}
432
433impl fmt::Display for Time {
434 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
435 let s = self.seconds();
436 let n = self.second_nanos();
437 write!(f, "{s}.{n}")
438 }
439}
440
441#[cfg(test)]
442mod tests {
443 use quickcheck::{Arbitrary, Gen, quickcheck, TestResult};
444 use super::{NANO, Time};
445
446 impl Arbitrary for Time {
447 fn arbitrary(g: &mut Gen) -> Self {
448 Time::from(u64::arbitrary(g))
449 }
450 }
451
452 quickcheck! {
453 fn prop_sec_nano_id(s: u64, n: u64) -> TestResult {
454 let n = n % NANO; if s.checked_mul(NANO).and_then(|s| s.checked_add(n)).is_none() {
456 return TestResult::discard()
457 }
458 let t = Time::try_from_sn(s, n).unwrap();
459 let b = t.seconds() == s && t.second_nanos() == n;
460 TestResult::from_bool(b)
461 }
462
463 fn prop_add_sub_nanos_id(t: Time, n: u64) -> TestResult {
464 let Ok(u) = t.add_nanos_checked(n) else {
465 return TestResult::discard()
466 };
467 TestResult::from_bool(u.sub_nanos(n) == t)
468 }
469
470 fn prop_add_sub_secs_id(t: Time, s: u64) -> TestResult {
471 let Ok(u) = t.add_seconds_checked(s) else {
472 return TestResult::discard()
473 };
474 TestResult::from_bool(u.sub_seconds(s) == t)
475 }
476
477 fn prop_add_sub_mins_id(t: Time, m: u64) -> TestResult {
478 let Ok(u) = t.add_minutes_checked(m) else {
479 return TestResult::discard()
480 };
481 TestResult::from_bool(u.sub_minutes(m) == t)
482 }
483
484 fn prop_add_sub_hours_id(t: Time, h: u64) -> TestResult {
485 let Ok(u) = t.add_hours_checked(h) else {
486 return TestResult::discard()
487 };
488 TestResult::from_bool(u.sub_hours(h) == t)
489 }
490
491 fn prop_add_sub_days_id(t: Time, d: u64) -> TestResult {
492 let Ok(u) = t.add_days_checked(d) else {
493 return TestResult::discard()
494 };
495 TestResult::from_bool(u.sub_days(d) == t)
496 }
497
498 fn prop_second_nanos(t: Time) -> bool {
499 t.nanos() - t.seconds() * NANO == t.second_nanos()
500 }
501
502 fn prop_add_1s_as_nanos(t: Time) -> TestResult {
503 if Time::MAX.try_sub(t).map(|d| d < NANO.into()).unwrap_or(true) {
504 return TestResult::discard()
505 }
506 let u = t.add_nanos(NANO);
507 let b = u.seconds() - t.seconds() == 1 && u.second_nanos() - t.second_nanos() == 0;
508 TestResult::from_bool(b)
509 }
510 }
511
512 #[test]
513 fn max_time() {
514 assert!(Time::MAX.add_nanos_checked(1).is_err());
515 assert!(Time::MAX.add_seconds_checked(1).is_err());
516 assert!(Time::MAX.add_minutes_checked(1).is_err());
517 assert!(Time::MAX.add_hours_checked(1).is_err());
518 assert!(Time::MAX.add_days_checked(1).is_err());
519 }
520
521 #[test]
522 fn min_time() {
523 assert!(Time::MIN.sub_nanos_checked(1).is_err());
524 assert!(Time::MIN.sub_seconds_checked(1).is_err());
525 assert!(Time::MIN.sub_minutes_checked(1).is_err());
526 assert!(Time::MIN.sub_hours_checked(1).is_err());
527 assert!(Time::MIN.sub_days_checked(1).is_err());
528 }
529}