Skip to main content

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}