1use crate::u128_is_multiple_of;
2use core::fmt::Debug;
3use core::fmt::Display;
4use core::num::NonZeroU128;
5use core::num::NonZeroU16;
6use core::ops::Deref;
7use core::ops::DerefMut;
8use core::str::FromStr;
9use core::time::Duration as StdDuration;
10
11#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
18#[cfg_attr(all(test, feature = "std"), derive(arbitrary::Arbitrary))]
19#[repr(transparent)]
20pub struct Duration(pub StdDuration);
21
22impl Duration {
23 pub const MAX_STRING_LEN: usize = 31;
25}
26
27impl Display for Duration {
28 #[allow(clippy::assign_op_pattern)]
29 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
30 let mut duration = self.0.as_nanos();
31 let unit = if duration == 0 {
32 "s"
33 } else {
34 let mut unit = "ns";
35 for u in UNITS {
36 let d: NonZeroU128 = u.0.into();
37 if !u128_is_multiple_of(duration, d.into()) {
38 break;
39 }
40 duration = duration / d;
41 unit = u.1;
42 }
43 unit
44 };
45 write!(f, "{duration}{unit}")
46 }
47}
48
49impl FromStr for Duration {
50 type Err = DurationError;
51 fn from_str(other: &str) -> Result<Self, Self::Err> {
52 let other = other.trim();
53 match other.rfind(char::is_numeric) {
54 None => Err(DurationError),
55 Some(i) => {
56 let duration: u128 = other[..=i].parse().map_err(|_| DurationError)?;
57 let unit = other[(i + 1)..].trim();
58 let factor = unit_to_factor(unit)? as u128;
59 let duration = duration.checked_mul(factor).ok_or(DurationError)?;
60 let seconds: u64 = (duration / NANOS_PER_SEC as u128)
61 .try_into()
62 .map_err(|_| DurationError)?;
63 let nanoseconds = (duration % NANOS_PER_SEC as u128) as u32;
64 Ok(Self(StdDuration::new(seconds, nanoseconds)))
65 }
66 }
67 }
68}
69
70impl From<StdDuration> for Duration {
71 fn from(other: StdDuration) -> Self {
72 Self(other)
73 }
74}
75
76impl From<Duration> for StdDuration {
77 fn from(other: Duration) -> Self {
78 other.0
79 }
80}
81
82impl Deref for Duration {
83 type Target = StdDuration;
84
85 fn deref(&self) -> &Self::Target {
86 &self.0
87 }
88}
89
90impl DerefMut for Duration {
91 fn deref_mut(&mut self) -> &mut Self::Target {
92 &mut self.0
93 }
94}
95
96fn unit_to_factor(unit: &str) -> Result<u64, DurationError> {
97 match unit {
98 "ns" => Ok(1_u64),
99 "μs" => Ok(1000_u64),
100 "ms" => Ok(1000_u64 * 1000_u64),
101 "s" | "" => Ok(1000_u64 * 1000_u64 * 1000_u64),
102 "m" => Ok(60_u64 * 1000_u64 * 1000_u64 * 1000_u64),
103 "h" => Ok(60_u64 * 60_u64 * 1000_u64 * 1000_u64 * 1000_u64),
104 "d" => Ok(24_u64 * 60_u64 * 60_u64 * 1000_u64 * 1000_u64 * 1000_u64),
105 _ => Err(DurationError),
106 }
107}
108
109#[derive(Debug)]
111pub struct DurationError;
112
113impl Display for DurationError {
114 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
115 Debug::fmt(self, f)
116 }
117}
118
119#[cfg(feature = "std")]
120impl std::error::Error for DurationError {}
121
122const UNITS: [(NonZeroU16, &str); 6] = [
123 (unsafe { NonZeroU16::new_unchecked(1000) }, "μs"),
124 (unsafe { NonZeroU16::new_unchecked(1000) }, "ms"),
125 (unsafe { NonZeroU16::new_unchecked(1000) }, "s"),
126 (unsafe { NonZeroU16::new_unchecked(60) }, "m"),
127 (unsafe { NonZeroU16::new_unchecked(60) }, "h"),
128 (unsafe { NonZeroU16::new_unchecked(24) }, "d"),
129];
130
131const NANOS_PER_SEC: u32 = 1_000_000_000_u32;
132
133#[cfg(all(test, feature = "std"))]
134mod tests {
135
136 use std::ops::AddAssign;
137
138 use arbtest::arbtest;
139
140 use super::*;
141
142 #[test]
143 fn test_duration_display() {
144 assert_eq!("123s", Duration(StdDuration::from_secs(123)).to_string());
145 assert_eq!("2m", Duration(StdDuration::from_secs(120)).to_string());
146 assert_eq!(
147 "1d",
148 Duration(StdDuration::from_secs(24 * 60 * 60)).to_string()
149 );
150 assert_eq!(
151 "23h",
152 Duration(StdDuration::from_secs(23 * 60 * 60)).to_string()
153 );
154 assert_eq!("0s", Duration(StdDuration::from_secs(0)).to_string());
155 assert_eq!("1μs", Duration(StdDuration::from_nanos(1000)).to_string());
156 }
157
158 #[test]
159 fn test_duration_parse() {
160 assert_eq!(Duration(StdDuration::from_secs(1)), "1".parse().unwrap());
161 assert_eq!(
162 Duration(StdDuration::from_nanos(1000)),
163 "1μs".parse().unwrap()
164 );
165 assert_eq!(Duration(StdDuration::from_secs(120)), "2m".parse().unwrap());
166 assert_eq!(
167 "Err(DurationError)",
168 format!("{:?}", "2km".parse::<Duration>())
169 );
170 assert_eq!(
171 "Err(DurationError)",
172 format!("{:?}", "ms".parse::<Duration>())
173 );
174 assert_eq!(
175 "Err(DurationError)",
176 format!("{:?}", format!("{}0", u128::MAX).parse::<Duration>())
177 );
178 }
179
180 #[test]
181 fn test_deref() {
182 assert_eq!(
183 StdDuration::from_secs(1),
184 *Duration(StdDuration::from_secs(1)),
185 );
186 let mut tmp = Duration(StdDuration::from_secs(1));
187 tmp.add_assign(StdDuration::from_secs(1));
188 assert_eq!(StdDuration::from_secs(2), *tmp);
189 }
190
191 #[test]
192 fn test_from_into() {
193 let d1 = Duration(StdDuration::from_secs(1));
194 let d2: StdDuration = d1.into();
195 let d3: Duration = d2.into();
196 assert_eq!(d1, d3);
197 assert_eq!(d1.0, d2);
198 }
199
200 #[test]
201 fn from_str_overflow_does_not_panic() {
202 let expected = u64::MAX;
203 let string = format!("{expected}d");
204 assert!(string.parse::<Duration>().is_err(), "string = {string:?}");
205 let expected = u128::MAX;
206 let string = format!("{expected}d");
207 assert!(string.parse::<Duration>().is_err(), "string = {string:?}");
208 }
209
210 #[test]
211 fn display_parse_symmetry() {
212 arbtest(|u| {
213 let expected: Duration = u.arbitrary()?;
214 let string = expected.to_string();
215 let actual: Duration = string.parse().unwrap();
216 assert_eq!(expected, actual, "string = `{string}`");
217 Ok(())
218 });
219 }
220
221 #[test]
222 fn parse_display_symmetry() {
223 arbtest(|u| {
224 let (unit, max) = *u
225 .choose(&[
226 ("ns", MAX_NANOSECONDS),
227 ("μs", MAX_NANOSECONDS / 1000_u128),
228 ("ms", MAX_NANOSECONDS / (1000_u128 * 1000_u128)),
229 ("s", MAX_NANOSECONDS / (1000_u128 * 1000_u128 * 1000_u128)),
230 (
231 "m",
232 MAX_NANOSECONDS / (1000_u128 * 1000_u128 * 1000_u128 * 60_u128),
233 ),
234 (
235 "h",
236 MAX_NANOSECONDS / (1000_u128 * 1000_u128 * 1000_u128 * 60_u128 * 60_u128),
237 ),
238 (
239 "d",
240 MAX_NANOSECONDS
241 / (1000_u128 * 1000_u128 * 1000_u128 * 60_u128 * 60_u128 * 24_u128),
242 ),
243 ])
244 .unwrap();
245 let number: u128 = u.int_in_range(0_u128..=max)?;
246 let prefix = *u.choose(&["", " ", " "]).unwrap();
247 let infix = *u.choose(&["", " ", " "]).unwrap();
248 let suffix = *u.choose(&["", " ", " "]).unwrap();
249 let expected = format!("{prefix}{number}{infix}{unit}{suffix}");
250 let expected_duration: Duration = expected.parse().unwrap();
251 let actual = expected_duration.to_string();
252 let actual_duration: Duration = actual.parse().unwrap();
253 assert_eq!(
254 expected_duration, actual_duration,
255 "string 1 = `{expected}`, string 2 = `{actual}`"
256 );
257 assert!(
258 expected == actual
259 || u128_is_multiple_of(actual_duration.0.as_nanos(), number)
260 || number == 0
261 );
262 Ok(())
263 });
264 }
265
266 const MAX_NANOSECONDS: u128 =
267 (u64::MAX as u128) * (NANOS_PER_SEC as u128) + (NANOS_PER_SEC as u128) - 1_u128;
268}