1use core::ops::{Add, Mul, Neg, Sub};
9
10use nom::{
11 IResult, Parser, branch::alt, bytes::complete::tag, character::complete::char, combinator::opt, multi::many1,
12 number::complete::double,
13};
14
15const NANOS_PER_SECOND: i128 = 1_000_000_000;
16
17#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
19pub struct Duration {
20 nanos: i128,
21}
22
23impl Duration {
24 pub const ZERO: Duration = Duration {
26 nanos: 0,
27 };
28
29 #[must_use]
31 pub const fn from_nanos(nanos: i128) -> Duration {
32 Duration {
33 nanos,
34 }
35 }
36
37 #[must_use]
39 pub const fn nanoseconds(nanos: i64) -> Duration {
40 Duration {
41 nanos: nanos as i128,
42 }
43 }
44
45 #[must_use]
47 pub const fn microseconds(micros: i64) -> Duration {
48 Duration {
49 nanos: micros as i128 * 1_000,
50 }
51 }
52
53 #[must_use]
55 pub const fn milliseconds(millis: i64) -> Duration {
56 Duration {
57 nanos: millis as i128 * 1_000_000,
58 }
59 }
60
61 #[must_use]
63 pub const fn seconds(seconds: i64) -> Duration {
64 Duration {
65 nanos: seconds as i128 * NANOS_PER_SECOND,
66 }
67 }
68
69 #[must_use]
71 pub const fn minutes(minutes: i64) -> Duration {
72 Duration {
73 nanos: minutes as i128 * 60 * NANOS_PER_SECOND,
74 }
75 }
76
77 #[must_use]
79 pub const fn hours(hours: i64) -> Duration {
80 Duration {
81 nanos: hours as i128 * 3600 * NANOS_PER_SECOND,
82 }
83 }
84
85 #[must_use]
87 pub const fn as_nanos(self) -> i128 {
88 self.nanos
89 }
90
91 #[must_use]
94 pub const fn num_nanoseconds(self) -> Option<i64> {
95 if self.nanos >= i64::MIN as i128 && self.nanos <= i64::MAX as i128 {
96 Some(self.nanos as i64)
97 } else {
98 None
99 }
100 }
101
102 #[must_use]
104 pub const fn num_seconds(self) -> i64 {
105 (self.nanos / NANOS_PER_SECOND) as i64
106 }
107
108 #[must_use]
110 pub const fn is_zero(self) -> bool {
111 self.nanos == 0
112 }
113
114 #[must_use]
116 pub const fn is_negative(self) -> bool {
117 self.nanos < 0
118 }
119
120 #[must_use]
122 pub const fn checked_add(self, other: Duration) -> Option<Duration> {
123 match self.nanos.checked_add(other.nanos) {
124 Some(nanos) => Some(Duration {
125 nanos,
126 }),
127 None => None,
128 }
129 }
130
131 #[must_use]
133 pub const fn checked_sub(self, other: Duration) -> Option<Duration> {
134 match self.nanos.checked_sub(other.nanos) {
135 Some(nanos) => Some(Duration {
136 nanos,
137 }),
138 None => None,
139 }
140 }
141
142 pub(crate) const fn serialized_secs(self) -> i64 {
144 (self.nanos / NANOS_PER_SECOND) as i64
145 }
146
147 pub(crate) const fn serialized_nanos(self) -> i32 {
149 (self.nanos % NANOS_PER_SECOND) as i32
150 }
151
152 pub(crate) const fn from_secs_nanos(secs: i64, nanos: i32) -> Duration {
154 Duration {
155 nanos: secs as i128 * NANOS_PER_SECOND + nanos as i128,
156 }
157 }
158}
159
160impl core::fmt::Display for Duration {
161 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
162 f.write_str(&format_duration(self))
163 }
164}
165
166impl Add for Duration {
167 type Output = Duration;
168
169 fn add(self, rhs: Duration) -> Duration {
170 Duration {
171 nanos: self.nanos + rhs.nanos,
172 }
173 }
174}
175
176impl Sub for Duration {
177 type Output = Duration;
178
179 fn sub(self, rhs: Duration) -> Duration {
180 Duration {
181 nanos: self.nanos - rhs.nanos,
182 }
183 }
184}
185
186impl Mul<i32> for Duration {
187 type Output = Duration;
188
189 fn mul(self, rhs: i32) -> Duration {
190 Duration {
191 nanos: self.nanos * rhs as i128,
192 }
193 }
194}
195
196impl Neg for Duration {
197 type Output = Duration;
198
199 fn neg(self) -> Duration {
200 Duration {
201 nanos: -self.nanos,
202 }
203 }
204}
205
206pub fn parse_duration(i: &str) -> IResult<&str, Duration> {
226 let (i, neg) = opt(parse_negative).parse(i)?;
227 if i == "0" {
228 return Ok((i, Duration::ZERO));
229 }
230 let (i, duration) = many1(parse_number_unit)
231 .parse(i)
232 .map(|(i, d)| (i, d.iter().fold(Duration::ZERO, |acc, next| acc + *next)))?;
233 Ok((i, if neg.is_some() { -duration } else { duration }))
234}
235
236enum Unit {
237 Nanosecond,
238 Microsecond,
239 Millisecond,
240 Second,
241 Minute,
242 Hour,
243}
244
245impl Unit {
246 fn nanos(&self) -> i64 {
247 match self {
248 Unit::Nanosecond => 1,
249 Unit::Microsecond => 1_000,
250 Unit::Millisecond => 1_000_000,
251 Unit::Second => 1_000_000_000,
252 Unit::Minute => 60 * 1_000_000_000,
253 Unit::Hour => 60 * 60 * 1_000_000_000,
254 }
255 }
256}
257
258fn parse_number_unit(i: &str) -> IResult<&str, Duration> {
259 let (i, num) = double(i)?;
260 let (i, unit) = parse_unit(i)?;
261 let duration = to_duration(num, unit);
262 Ok((i, duration))
263}
264
265fn parse_negative(i: &str) -> IResult<&str, ()> {
266 let (i, _): (&str, char) = char('-').parse(i)?;
267 Ok((i, ()))
268}
269
270fn parse_unit(i: &str) -> IResult<&str, Unit> {
271 alt((
272 tag("ms").map(|_| Unit::Millisecond),
273 tag("us").map(|_| Unit::Microsecond),
274 tag("ns").map(|_| Unit::Nanosecond),
275 char('h').map(|_| Unit::Hour),
276 char('m').map(|_| Unit::Minute),
277 char('s').map(|_| Unit::Second),
278 ))
279 .parse(i)
280}
281
282fn to_duration(num: f64, unit: Unit) -> Duration {
283 Duration::from_nanos((num * unit.nanos() as f64).trunc() as i128)
284}
285
286pub fn format_duration(d: &Duration) -> String {
295 let buf = &mut [0u8; 32];
296 let mut w = buf.len();
297
298 let mut neg = false;
299 let mut u = d
300 .num_nanoseconds()
301 .map(|n| {
302 if n < 0 {
303 neg = true;
304 }
305 n as u64
306 })
307 .unwrap_or_else(|| {
308 let s = d.num_seconds();
309 if s < 0 {
310 neg = true;
311 }
312 s as u64 * SECOND
313 });
314
315 if u < SECOND {
316 let mut _prec = 0;
319 w -= 1;
320 buf[w] = b's';
321 w -= 1;
322
323 if u == 0 {
324 return "0s".to_string();
325 } else if u < MICROSECOND {
326 _prec = 0;
327 buf[w] = b'n';
328 } else if u < MILLISECOND {
329 _prec = 3;
330 buf[w] = 0xB5;
332 w -= 1;
333 buf[w] = 0xC2;
334 } else {
335 _prec = 6;
336 buf[w] = b'm';
337 }
338 (w, u) = format_float(&mut buf[..w], u, _prec);
339 w = format_int(&mut buf[..w], u);
340 } else {
341 w -= 1;
342 buf[w] = b's';
343 (w, u) = format_float(&mut buf[..w], u, 9);
344
345 w = format_int(&mut buf[..w], u % 60);
347 u /= 60;
348
349 if u > 0 {
351 w -= 1;
352 buf[w] = b'm';
353 w = format_int(&mut buf[..w], u % 60);
354 u /= 60;
355
356 if u > 0 {
358 w -= 1;
359 buf[w] = b'h';
360 w = format_int(&mut buf[..w], u);
361 }
362 }
363 }
364
365 if neg {
366 w -= 1;
367 buf[w] = b'-';
368 }
369 String::from_utf8_lossy(&buf[w..]).into_owned()
370}
371
372const SECOND: u64 = 1_000_000_000;
373const MILLISECOND: u64 = 1_000_000;
374const MICROSECOND: u64 = 1_000;
375
376fn format_float(buf: &mut [u8], mut v: u64, prec: usize) -> (usize, u64) {
377 let mut w = buf.len();
378 let mut print = false;
379 for _ in 0..prec {
380 let digit = v % 10;
381 print = print || digit != 0;
382 if print {
383 w -= 1;
384 buf[w] = digit as u8 + b'0';
385 }
386 v /= 10;
387 }
388 if print {
389 w -= 1;
390 buf[w] = b'.';
391 }
392 (w, v)
393}
394
395fn format_int(buf: &mut [u8], mut v: u64) -> usize {
396 let mut w = buf.len();
397 if v == 0 {
398 w -= 1;
399 buf[w] = b'0';
400 } else {
401 while v > 0 {
402 w -= 1;
403 buf[w] = (v % 10) as u8 + b'0';
404 v /= 10;
405 }
406 }
407 w
408}
409
410#[cfg(test)]
411mod tests {
412 use crate::duration::{Duration, format_duration, parse_duration};
413
414 fn assert_duration(input: &str, expected: Duration) {
415 let (_, duration) = parse_duration(input).unwrap();
416 assert_eq!(duration, expected, "{input}");
417 }
418
419 fn assert_print_duration(input: Duration, expected: &str) {
420 let actual = format_duration(&input);
421 assert_eq!(actual, expected, "{input}");
422 }
423
424 macro_rules! assert_durations {
425 ($($str:expr => $duration:expr),*$(,)?) => {
426 #[test]
427 fn test_durations() {
428 $(
429 assert_duration($str, $duration);
430 )*
431 }
432 };
433 }
434
435 macro_rules! assert_duration_format {
436 ($($duration:expr => $str:expr),*$(,)?) => {
437 #[test]
438 fn test_format_durations() {
439 $(
440 assert_print_duration($duration, $str);
441 )*
442 }
443 };
444 }
445
446 assert_durations! {
447 "1s" => Duration::seconds(1),
448 "-1s" => Duration::seconds(-1),
449 "1.1s" => Duration::seconds(1) + Duration::milliseconds(100),
450 "1.5m" => Duration::minutes(1) + Duration::seconds(30),
451 "1m1s" => Duration::minutes(1) + Duration::seconds(1),
452 "1h1m1s" => Duration::hours(1) + Duration::minutes(1) + Duration::seconds(1),
453 "1ms" => Duration::milliseconds(1),
454 "1us" => Duration::microseconds(1),
455 "1ns" => Duration::nanoseconds(1),
456 "1.1ns" => Duration::nanoseconds(1),
457 "1.123us" => Duration::microseconds(1) + Duration::nanoseconds(123),
458 "0s" => Duration::ZERO,
459 "0h0m0s" => Duration::ZERO,
460 "0h0m1s" => Duration::seconds(1),
461 "0" => Duration::ZERO,
462 "-0" => Duration::ZERO,
463 }
464
465 assert_duration_format! {
466 Duration::ZERO => "0s",
467 Duration::nanoseconds(1) => "1ns",
468 Duration::nanoseconds(1100) => "1.1µs",
469 Duration::microseconds(2200) => "2.2ms",
470 Duration::milliseconds(3300) => "3.3s",
471 Duration::minutes(4) + Duration::seconds(5) => "4m5s",
472 Duration::minutes(4) + Duration::milliseconds(5001) => "4m5.001s",
473 Duration::hours(5) + Duration::minutes(6) + Duration::milliseconds(7001) => "5h6m7.001s",
474 Duration::minutes(8) + Duration::nanoseconds(1) => "8m0.000000001s",
475 Duration::nanoseconds(i64::MAX) => "2562047h47m16.854775807s",
476 Duration::nanoseconds(i64::MIN) => "-2562047h47m16.854775808s",
477 }
478}