qubit_datatype/converter/duration_unit.rs
1/*******************************************************************************
2 *
3 * Copyright (c) 2025 - 2026 Haixing Hu.
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10//! # Duration Unit
11//!
12//! Defines supported units for duration conversion.
13//!
14
15use std::time::Duration;
16
17/// Unit used when converting [`Duration`] values to and from scalar values.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum DurationUnit {
20 /// Nanoseconds.
21 Nanoseconds,
22 /// Microseconds.
23 Microseconds,
24 /// Milliseconds.
25 Milliseconds,
26 /// Seconds.
27 Seconds,
28 /// Minutes.
29 Minutes,
30 /// Hours.
31 Hours,
32 /// Days.
33 Days,
34}
35
36impl DurationUnit {
37 /// Returns the canonical suffix for this duration unit.
38 ///
39 /// # Returns
40 ///
41 /// The ASCII suffix used when formatting this unit.
42 #[inline]
43 pub const fn suffix(self) -> &'static str {
44 match self {
45 DurationUnit::Nanoseconds => "ns",
46 DurationUnit::Microseconds => "us",
47 DurationUnit::Milliseconds => "ms",
48 DurationUnit::Seconds => "s",
49 DurationUnit::Minutes => "m",
50 DurationUnit::Hours => "h",
51 DurationUnit::Days => "d",
52 }
53 }
54
55 /// Parses a duration unit suffix.
56 ///
57 /// # Parameters
58 ///
59 /// * `suffix` - Candidate unit suffix.
60 ///
61 /// # Returns
62 ///
63 /// Returns the matched unit, or `None` when the suffix is unsupported.
64 #[inline]
65 pub fn from_suffix(suffix: &str) -> Option<Self> {
66 match suffix {
67 "ns" => Some(DurationUnit::Nanoseconds),
68 "us" | "µs" | "μs" => Some(DurationUnit::Microseconds),
69 "ms" => Some(DurationUnit::Milliseconds),
70 "s" => Some(DurationUnit::Seconds),
71 "m" => Some(DurationUnit::Minutes),
72 "h" => Some(DurationUnit::Hours),
73 "d" => Some(DurationUnit::Days),
74 _ => None,
75 }
76 }
77
78 /// Converts an integer value in this unit to a [`Duration`].
79 ///
80 /// # Parameters
81 ///
82 /// * `value` - Non-negative integer value expressed in this unit.
83 ///
84 /// # Returns
85 ///
86 /// The corresponding [`Duration`].
87 ///
88 /// # Errors
89 ///
90 /// Returns an error message when converting the value to seconds would
91 /// overflow the range supported by [`Duration`].
92 pub fn duration_from_u64(self, value: u64) -> Result<Duration, String> {
93 match self {
94 DurationUnit::Nanoseconds => Ok(Duration::from_nanos(value)),
95 DurationUnit::Microseconds => Ok(Duration::from_micros(value)),
96 DurationUnit::Milliseconds => Ok(Duration::from_millis(value)),
97 DurationUnit::Seconds => Ok(Duration::from_secs(value)),
98 DurationUnit::Minutes => checked_secs(value, 60, "minutes"),
99 DurationUnit::Hours => checked_secs(value, 60 * 60, "hours"),
100 DurationUnit::Days => checked_secs(value, 24 * 60 * 60, "days"),
101 }
102 }
103
104 /// Converts a [`Duration`] to this unit using half-up rounding.
105 ///
106 /// # Parameters
107 ///
108 /// * `duration` - Duration to format as an integer unit count.
109 ///
110 /// # Returns
111 ///
112 /// The rounded number of units represented by the duration.
113 pub fn rounded_units(self, duration: Duration) -> u128 {
114 let total_nanos = duration.as_nanos();
115 let unit_nanos = self.nanos_per_unit();
116 let quotient = total_nanos / unit_nanos;
117 let remainder = total_nanos % unit_nanos;
118 let rounding_threshold = unit_nanos.div_ceil(2);
119 if remainder >= rounding_threshold {
120 quotient + 1
121 } else {
122 quotient
123 }
124 }
125
126 /// Returns the number of nanoseconds in one unit.
127 ///
128 /// # Returns
129 ///
130 /// Nanoseconds per unit.
131 const fn nanos_per_unit(self) -> u128 {
132 match self {
133 DurationUnit::Nanoseconds => 1,
134 DurationUnit::Microseconds => 1_000,
135 DurationUnit::Milliseconds => 1_000_000,
136 DurationUnit::Seconds => 1_000_000_000,
137 DurationUnit::Minutes => 60 * 1_000_000_000,
138 DurationUnit::Hours => 60 * 60 * 1_000_000_000,
139 DurationUnit::Days => 24 * 60 * 60 * 1_000_000_000,
140 }
141 }
142}
143
144impl Default for DurationUnit {
145 /// Creates the default duration unit.
146 fn default() -> Self {
147 DurationUnit::Milliseconds
148 }
149}
150
151/// Converts a value multiplied by a second factor into a [`Duration`].
152///
153/// # Parameters
154///
155/// * `value` - Non-negative integer value.
156/// * `seconds_per_unit` - Number of seconds in each unit.
157/// * `unit_name` - Unit name used in overflow diagnostics.
158///
159/// # Returns
160///
161/// The corresponding [`Duration`].
162///
163/// # Errors
164///
165/// Returns an error message when the multiplication overflows `u64` seconds.
166fn checked_secs(value: u64, seconds_per_unit: u64, unit_name: &str) -> Result<Duration, String> {
167 value
168 .checked_mul(seconds_per_unit)
169 .map(Duration::from_secs)
170 .ok_or_else(|| format!("duration {unit_name} overflow u64 seconds"))
171}