1use std::fmt::{Debug, Display};
10use std::ops::{Add, Sub};
11
12use num_traits::CheckedSub;
13use serde::{Deserialize, Serialize};
14
15use crate::resolution_unit::ResolutionUnit;
16
17pub trait ToStringMilliseconds {
18 fn to_string_ms(&self) -> String;
21}
22
23pub trait ToStringSeconds {
24 fn to_string_seconds(&self) -> String;
27}
28
29pub trait FromMicroseconds: Sized {
30 fn from_usec(useconds: u64) -> Option<Self>;
31}
32
33pub trait ToNanoseconds {
34 fn to_nsec(self) -> u64;
35}
36
37pub trait ToIncrements {
39 fn to_increments(self) -> u64;
40}
41
42pub trait Time:
45 ToStringMilliseconds
46 + FromMicroseconds
47 + From<u64>
48 + Display
49 + ToNanoseconds
50 + Debug
51 + Copy
52 + ToIncrements
53{
54}
55
56impl Time for MicroTime {}
57impl ResolutionUnit for MicroTime {
58 const RESOLUTION_UNIT_SHORT: &str = "us";
59}
60impl Time for NanoTime {}
61impl ResolutionUnit for NanoTime {
62 const RESOLUTION_UNIT_SHORT: &str = "ns";
63}
64
65fn print_milli_micro(f: &mut std::fmt::Formatter<'_>, milli: u32, micro: u32) -> std::fmt::Result {
66 write!(f, "{milli}.{micro:03} ms")
67}
68
69fn print_milli_micro_nano(
70 f: &mut std::fmt::Formatter<'_>,
71 milli: u32,
72 micro: u32,
73 nano: u32,
74) -> std::fmt::Result {
75 write!(f, "{milli}.{micro:03}_{nano:03} ms")
76}
77
78fn print_micro_nano(f: &mut std::fmt::Formatter<'_>, micro: u32, nano: u32) -> std::fmt::Result {
79 write!(f, "{micro}.{nano:03} us")
80}
81
82macro_rules! define_time {
83 { $_Time:ident, $_sec:tt, $max__sec:tt } => {
84
85 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
86 #[serde(deny_unknown_fields)]
87 pub struct $_Time {
88 sec: u32,
89 $_sec: u32,
90 }
91
92 impl $_Time {
93 pub fn check(self) {
96 assert!(self.is_valid())
97 }
98
99 pub fn is_valid(self)-> bool {
100 self.$_sec < $max__sec
101 }
102
103 pub fn new(sec: u32, $_sec: u32) -> Option<Self> {
104 let slf = Self { sec, $_sec };
105 if slf.is_valid() {
106 Some(slf)
107 } else {
108 None
109 }
110 }
111
112 pub fn sec(self) -> u32 { self.sec }
113 pub fn $_sec(self) -> u32 { self.$_sec }
114 }
115
116 impl ToIncrements for $_Time {
117 fn to_increments(self) -> u64 {
118 u64::from(self.sec) * $max__sec + u64::from(self.$_sec)
119 }
120 }
121
122 impl Add for $_Time {
123 type Output = $_Time;
124
125 fn add(self, rhs: Self) -> Self::Output {
126 const CUTOFF: u32 = $max__sec;
127 let $_sec = self.$_sec + rhs.$_sec;
128 if $_sec >= CUTOFF {
129 Self {
130 sec: self.sec + rhs.sec + 1,
131 $_sec: $_sec - CUTOFF,
132 }
133 } else {
134 Self {
135 sec: self.sec + rhs.sec,
136 $_sec,
137 }
138 }
139 }
140 }
141
142 impl Sub for $_Time {
143 type Output = $_Time;
144
145 fn sub(self, rhs: Self) -> Self::Output {
146 self.checked_sub(&rhs).expect("number overflow")
147 }
148 }
149
150 impl CheckedSub for $_Time {
151 fn checked_sub(&self, rhs: &Self) -> Option<Self> {
152 let sec = self.sec.checked_sub(rhs.sec)?;
153 match self.$_sec.checked_sub(rhs.$_sec) {
154 Some($_sec) => Some(Self { sec, $_sec }),
155 None => Some(Self {
156 sec: sec - 1,
157 $_sec: (self.$_sec + $max__sec) - rhs.$_sec,
158 }),
159 }
160 }
161 }
162 }
163}
164
165define_time!(MicroTime, usec, 1_000_000);
168
169impl MicroTime {
170 fn to_usec(self) -> u64 {
171 self.sec as u64 * 1_000_000 + (self.usec as u64)
172 }
173}
174
175impl FromMicroseconds for MicroTime {
176 fn from_usec(useconds: u64) -> Option<Self> {
177 let sec = useconds / 1_000_000;
178 let usec = useconds % 1_000_000;
179 Some(Self {
180 sec: sec.try_into().ok()?,
181 usec: usec.try_into().expect("always in range"),
182 })
183 }
184}
185
186impl ToNanoseconds for MicroTime {
187 fn to_nsec(self) -> u64 {
188 self.sec as u64 * 1_000_000_000 + (self.usec as u64 * 1000)
189 }
190}
191
192impl From<u64> for MicroTime {
195 fn from(value: u64) -> Self {
196 Self::from_usec(value).expect("outside representable range")
197 }
198}
199
200impl From<MicroTime> for u64 {
202 fn from(value: MicroTime) -> Self {
203 value.to_usec()
204 }
205}
206
207fn milli_micro(usec: u32) -> (u32, u32) {
208 (usec / 1000, usec % 1000)
209}
210
211fn format_integer_with_undercores(digits: &str) -> String {
212 digits
213 .as_bytes()
214 .rchunks(3)
215 .rev()
216 .map(std::str::from_utf8)
217 .collect::<Result<Vec<&str>, _>>()
218 .unwrap()
219 .join("_")
220}
221
222impl Display for MicroTime {
223 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224 let Self { sec, usec } = *self;
225 if sec >= 1 {
226 let (milli, micro) = milli_micro(usec);
227 let sec_str = format_integer_with_undercores(&sec.to_string());
228 write!(f, "{sec_str}.{milli:03}_{micro:03} s")
229 } else if usec >= 1_000 {
230 let (milli, micro) = milli_micro(usec);
231 print_milli_micro(f, milli, micro)
232 } else {
233 let usec_str = format_integer_with_undercores(&usec.to_string());
234 write!(f, "{usec_str} us")
235 }
236 }
237}
238
239define_time!(NanoTime, nsec, 1_000_000_000);
242
243impl NanoTime {
244 pub fn from_nsec(nseconds: u64) -> Option<Self> {
245 let sec = nseconds / 1_000_000_000;
246 let nsec = nseconds % 1_000_000_000;
247 Some(Self {
248 sec: sec.try_into().ok()?,
249 nsec: nsec.try_into().expect("always in range"),
250 })
251 }
252}
253
254impl FromMicroseconds for NanoTime {
255 fn from_usec(useconds: u64) -> Option<Self> {
256 let nsec = useconds.checked_mul(1000)?;
257 Self::from_nsec(nsec)
258 }
259}
260
261impl ToNanoseconds for NanoTime {
262 fn to_nsec(self) -> u64 {
263 self.sec as u64 * 1_000_000_000 + self.nsec as u64
264 }
265}
266
267impl From<MicroTime> for NanoTime {
268 fn from(value: MicroTime) -> Self {
269 NanoTime {
270 sec: value.sec,
271 nsec: value.usec * 1000,
272 }
273 }
274}
275
276impl From<u64> for NanoTime {
279 fn from(value: u64) -> Self {
280 Self::from_nsec(value).expect("outside representable range")
281 }
282}
283
284impl From<NanoTime> for u64 {
286 fn from(value: NanoTime) -> Self {
287 value.to_nsec()
288 }
289}
290
291fn milli_micro_nano(nsec: u32) -> (u32, u32, u32) {
292 let usec = nsec / 1000;
293 let nano = nsec % 1000;
294 let (milli, micro) = milli_micro(usec);
295 (milli, micro, nano)
296}
297
298impl Display for NanoTime {
299 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
300 let Self { sec, nsec } = *self;
301 if sec >= 1 {
302 let (milli, micro, nano) = milli_micro_nano(nsec);
303 let sec_str = format_integer_with_undercores(&sec.to_string());
304 write!(f, "{sec_str}.{milli:03}_{micro:03}_{nano:03} s")
305 } else {
306 let (milli, micro, nano) = milli_micro_nano(nsec);
307 if milli > 0 {
308 print_milli_micro_nano(f, milli, micro, nano)
309 } else if micro > 0 {
310 print_micro_nano(f, micro, nano)
311 } else {
312 let nsec_str = format_integer_with_undercores(&nsec.to_string());
313 write!(f, "{nsec_str} ns")
314 }
315 }
316 }
317}
318
319impl ToStringMilliseconds for MicroTime {
320 fn to_string_ms(&self) -> String {
321 let ms = self.sec * 1000 + self.usec / 1_000;
322 let usec_rest = self.usec % 1_000;
323 format!("{ms}.{usec_rest:03}")
324 }
325}
326
327impl ToStringMilliseconds for NanoTime {
328 fn to_string_ms(&self) -> String {
329 let ms = self.sec * 1000 + self.nsec / 1_000_000;
330 let nsec_rest = self.nsec % 1_000_000;
331 format!("{ms}.{nsec_rest:06}")
332 }
333}
334
335impl ToStringSeconds for MicroTime {
336 fn to_string_seconds(&self) -> String {
337 let Self { sec, usec } = self;
338 format!("{sec}.{usec:06}")
339 }
340}
341
342impl ToStringSeconds for NanoTime {
343 fn to_string_seconds(&self) -> String {
344 let Self { sec, nsec } = self;
345 format!("{sec}.{nsec:09}")
346 }
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352 use crate::digit_num::{Digit, DigitNum, DigitNumFormat};
353 use rand::Rng;
354
355 #[test]
356 fn t_micro_time() {
357 let a = MicroTime::new(2, 999_999).unwrap();
358 let b = MicroTime::new(3, 1).unwrap();
359 assert_eq!(a + b, MicroTime::new(6, 0).unwrap());
360 assert_eq!(
361 a + MicroTime::new(0, 2).unwrap(),
362 MicroTime::new(3, 1).unwrap()
363 );
364 let t = |sec, usec| MicroTime::new(sec, usec).unwrap();
365 assert_eq!(t(10, 2) - t(10, 1), t(0, 1));
366 assert_eq!(t(10, 2) - t(10, 2), t(0, 0));
367 assert_eq!(t(11, 2) - t(10, 2), t(1, 0));
369 assert_eq!(t(11, 2) - t(10, 1), t(1, 1));
370 assert_eq!(t(11, 2) - t(10, 3), t(0, 999_999));
371 assert_eq!(b - a, MicroTime::new(0, 2).unwrap());
372 assert_eq!(t(0, 999_999) + t(3, 999_999), t(4, 999_998));
373 assert_eq!(t(4, 999_998) - t(3, 999_999), t(0, 999_999));
374 assert_eq!(t(4, 999_998) - t(0, 999_999), t(3, 999_999));
375 }
376
377 #[test]
378 #[should_panic]
379 fn t_micro_time_panic() {
380 let a = MicroTime::new(2, 999_999).unwrap();
381 let b = MicroTime::new(3, 1).unwrap();
382 let _ = a - b;
383 }
384
385 #[test]
386 #[should_panic]
387 fn t_micro_time_panic_new() {
388 let _ = MicroTime::new(2, 1_000_000).unwrap();
389 }
390
391 #[test]
392 fn t_nano_time() {
393 let t = |sec, nsec| NanoTime::new(sec, nsec).unwrap();
394 assert_eq!(t(4, 999_998) - t(0, 999_999), t(3, 999_999_999));
395 assert_eq!(t(4, 999_999) - t(0, 999_998), t(4, 1));
396
397 assert_eq!(t(0, 999_999_999) + t(3, 999_999_999), t(4, 999_999_998));
398 assert_eq!(t(4, 999_999_998) - t(3, 999_999_999), t(0, 999_999_999));
399 assert_eq!(t(4, 999_999_998) - t(0, 999_999_999), t(3, 999_999_999));
400 }
401
402 #[test]
403 fn t_nano_time_convert() {
404 let n = |sec, nsec| NanoTime::new(sec, nsec).unwrap();
405 let u = |sec, usec| MicroTime::new(sec, usec).unwrap();
406 assert_eq!(
407 {
408 let x: NanoTime = u(3, 490_000).into();
409 x
410 },
411 n(3, 490_000_000),
412 );
413 assert_eq!(u(8, 30).to_nsec(), n(8, 30_000).to_nsec(),);
414
415 assert_eq!(
416 NanoTime::from_nsec(u(8, 30).to_nsec()).unwrap(),
417 n(8, 30_000)
418 );
419 }
420
421 fn test_stringification<
422 const DIGITS_BELOW_MS: usize,
423 const DIGITS_BELOW_S: usize,
424 Time: ToStringMilliseconds
425 + ToStringSeconds
426 + FromMicroseconds
427 + From<u64>
428 + Display
429 + ToNanoseconds
430 + Debug
431 + Copy
432 + ToIncrements,
433 >() {
434 let mut num: DigitNum<DIGITS_BELOW_MS> = DigitNum::new();
435 let mut num_seconds: DigitNum<DIGITS_BELOW_S> = DigitNum::new();
436 let digits_above_ms = 10; let num_digits_to_test = DIGITS_BELOW_MS + digits_above_ms;
438 for _ in 0..num_digits_to_test {
439 let num_u64: u64 = (&num).try_into().unwrap();
440 let time = Time::from(num_u64);
441
442 assert_eq!(
444 time.to_string_ms(),
445 num.to_string_with_params(DigitNumFormat {
446 underscores: false,
447 omit_trailing_dot: false
448 })
449 );
450
451 assert_eq!(
453 time.to_string_seconds(),
454 num_seconds.to_string_with_params(DigitNumFormat {
455 underscores: false,
456 omit_trailing_dot: false
457 })
458 );
459
460 let time_str = format!("{time}");
462 let parts: Vec<_> = time_str.split(' ').collect();
463 let (number, num_digits_below_seconds) = match parts.as_slice() {
464 &[number, "ns"] => (number, 9),
465 &[number, "us"] => (number, 6),
466 &[number, "ms"] => (number, 3),
467 &[number, "s"] => (number, 0),
468 _ => unreachable!(),
469 };
470 let (expect_dot, expect_digits_after_dot) =
471 if DIGITS_BELOW_S == num_digits_below_seconds {
472 (false, 0)
473 } else {
474 (true, DIGITS_BELOW_S - num_digits_below_seconds)
475 };
476 let parts: Vec<&str> = number.split('.').collect();
477 let digits = match parts.as_slice() {
478 [left, right] => {
479 assert!(expect_dot);
480 let right_without_underscores = right.replace("_", "");
483 assert_eq!(right_without_underscores.len(), expect_digits_after_dot);
484 format!("{left}_{right}")
485 }
486 [left_only] => {
487 assert!(!expect_dot);
488 format!("{left_only}")
489 }
490 _ => unreachable!(),
491 };
492 let num_in_increments: DigitNum<0> = num.clone().into_changed_dot_position();
493 assert_eq!(num_u64, u64::try_from(&num_in_increments).unwrap());
494 assert_eq!(
495 digits,
496 num_in_increments.to_string_with_params(DigitNumFormat {
497 underscores: true,
498 omit_trailing_dot: true
499 })
500 );
501
502 assert_eq!(time.to_increments(), num_u64);
504
505 let ns = time.to_nsec();
507 let num_u64_multiplicator = match DIGITS_BELOW_S {
509 6 => 1000,
510 9 => 1,
511 _ => unreachable!(),
512 };
513 assert_eq!(ns, num_u64 * num_u64_multiplicator);
514
515 let time2 = Time::from_usec(num_u64).expect("works for MicroTime::from, so also here");
517 let ns2 = time2.to_nsec();
518 assert_eq!(ns2, num_u64 * 1000);
519
520 let digit = Digit::random();
521 num.push_lowest_digit(digit);
522 num_seconds.push_lowest_digit(digit);
523 }
524 }
525
526 #[test]
527 fn t_micro_time_stringification() {
528 test_stringification::<3, 6, MicroTime>();
529 }
530
531 #[test]
532 fn t_nano_time_stringification() {
533 test_stringification::<6, 9, NanoTime>();
534 }
535
536 fn test_arithmetic<Time: From<u64> + Display + Debug + Copy + Add + Sub>()
537 where
538 <Time as Add>::Output: ToIncrements,
539 <Time as Sub>::Output: ToIncrements,
540 {
541 let mut rng = rand::thread_rng();
542
543 for _ in 0..100000 {
544 let max = 2147483647500000;
546 let a: u64 = rng.gen_range(0..max);
547 let ta = Time::from(a);
548 let b: u64 = rng.gen_range(0..max);
549 let tb = Time::from(b);
550 let c = a + b;
551 let tc = ta + tb;
552 assert_eq!(c, tc.to_increments());
553 let (d, td) = if a > b {
554 (a - b, ta - tb)
555 } else {
556 (b - a, tb - ta)
557 };
558 assert_eq!(d, td.to_increments());
559 }
560 }
561
562 #[test]
563 fn t_micro_time_arithmetic() {
564 test_arithmetic::<MicroTime>();
565 }
566
567 #[test]
568 fn t_nano_time_arithmetic() {
569 test_arithmetic::<NanoTime>();
570 }
571}