tuwunel_core/utils/
time.rs1pub mod exponential_backoff;
2
3use std::time::{Duration, SystemTime, UNIX_EPOCH};
4
5use crate::{Result, err};
6
7#[inline]
8#[must_use]
9#[expect(clippy::as_conversions, clippy::cast_possible_truncation)]
10pub fn now_millis() -> u64 { now().as_millis() as u64 }
11
12#[inline]
13#[must_use]
14pub fn now_secs() -> u64 { now().as_secs() }
15
16#[inline]
17#[must_use]
18pub fn now() -> Duration {
19 UNIX_EPOCH
20 .elapsed()
21 .expect("positive duration after epoch")
22}
23
24#[inline]
25#[must_use]
26pub fn duration_since_epoch(timepoint: SystemTime) -> Duration {
27 timepoint
28 .duration_since(UNIX_EPOCH)
29 .unwrap_or(Duration::ZERO)
30}
31
32#[inline]
33pub fn timepoint_from_epoch(duration: Duration) -> Result<SystemTime> {
34 UNIX_EPOCH
35 .checked_add(duration)
36 .ok_or_else(|| err!(Arithmetic("Duration {duration:?} from epoch is too large")))
37}
38
39#[inline]
40pub fn timepoint_from_now(duration: Duration) -> Result<SystemTime> {
41 SystemTime::now()
42 .checked_add(duration)
43 .ok_or_else(|| err!(Arithmetic("Duration {duration:?} from now is too large")))
44}
45
46#[inline]
47pub fn timepoint_ago(duration: Duration) -> Result<SystemTime> {
48 SystemTime::now()
49 .checked_sub(duration)
50 .ok_or_else(|| err!(Arithmetic("Duration {duration:?} ago is too large")))
51}
52
53#[inline]
54pub fn parse_timepoint_ago(ago: &str) -> Result<SystemTime> {
55 timepoint_ago(parse_duration(ago)?)
56}
57
58#[inline]
59pub fn parse_duration(duration: &str) -> Result<Duration> {
60 cyborgtime::parse_duration(duration)
61 .map_err(|error| err!("'{duration:?}' is not a valid duration string: {error:?}"))
62}
63
64#[inline]
65#[must_use]
66pub fn timepoint_has_passed(timepoint: SystemTime) -> bool {
67 SystemTime::now()
68 .duration_since(timepoint)
69 .is_ok()
70}
71
72#[must_use]
73pub fn rfc2822_from_seconds(epoch: i64) -> String {
74 use chrono::{DateTime, Utc};
75
76 DateTime::<Utc>::from_timestamp(epoch, 0)
77 .unwrap_or_default()
78 .to_rfc2822()
79}
80
81#[must_use]
82pub fn format(ts: SystemTime, str: &str) -> String {
83 use chrono::{DateTime, Utc};
84
85 let dt: DateTime<Utc> = ts.into();
86 dt.format(str).to_string()
87}
88
89#[must_use]
90#[expect(
91 clippy::as_conversions,
92 clippy::cast_possible_truncation,
93 clippy::cast_sign_loss
94)]
95pub fn pretty(d: Duration) -> String {
96 use Unit::*;
97
98 let fmt = |w, f, u| format!("{w}.{f} {u}");
99 let gen64 = |w, f, u| fmt(w, (f * 100.0) as u32, u);
100 let gen128 = |w, f, u| gen64(u64::try_from(w).expect("u128 to u64"), f, u);
101 match whole_and_frac(d) {
102 | (Days(whole), frac) => gen64(whole, frac, "days"),
103 | (Hours(whole), frac) => gen64(whole, frac, "hours"),
104 | (Mins(whole), frac) => gen64(whole, frac, "minutes"),
105 | (Secs(whole), frac) => gen64(whole, frac, "seconds"),
106 | (Millis(whole), frac) => gen128(whole, frac, "milliseconds"),
107 | (Micros(whole), frac) => gen128(whole, frac, "microseconds"),
108 | (Nanos(whole), frac) => gen128(whole, frac, "nanoseconds"),
109 }
110}
111
112#[must_use]
116#[expect(clippy::as_conversions, clippy::cast_precision_loss)]
117pub fn whole_and_frac(d: Duration) -> (Unit, f64) {
118 use Unit::*;
119
120 let whole = whole_unit(d);
121 (whole, match whole {
122 | Days(_) => (d.as_secs() % 86_400) as f64 / 86_400.0,
123 | Hours(_) => (d.as_secs() % 3_600) as f64 / 3_600.0,
124 | Mins(_) => (d.as_secs() % 60) as f64 / 60.0,
125 | Secs(_) => f64::from(d.subsec_millis()) / 1000.0,
126 | Millis(_) => f64::from(d.subsec_micros()) / 1000.0,
127 | Micros(_) => f64::from(d.subsec_nanos()) / 1000.0,
128 | Nanos(_) => 0.0,
129 })
130}
131
132#[must_use]
135pub fn whole_unit(d: Duration) -> Unit {
136 use Unit::*;
137
138 match d.as_secs() {
139 | 86_400.. => Days(d.as_secs() / 86_400),
140 | 3_600..=86_399 => Hours(d.as_secs() / 3_600),
141 | 60..=3_599 => Mins(d.as_secs() / 60),
142 | _ => match d.as_micros() {
143 | 1_000_000.. => Secs(d.as_secs()),
144 | 1_000..=999_999 => Millis(d.subsec_millis().into()),
145 | _ => match d.as_nanos() {
146 | 1_000.. => Micros(d.subsec_micros().into()),
147 | _ => Nanos(d.subsec_nanos().into()),
148 },
149 },
150 }
151}
152
153#[derive(Eq, PartialEq, Clone, Copy, Debug)]
154pub enum Unit {
155 Days(u64),
156 Hours(u64),
157 Mins(u64),
158 Secs(u64),
159 Millis(u128),
160 Micros(u128),
161 Nanos(u128),
162}