parse_frequency/
lib.rs

1use std::{
2    fmt::Display,
3    ops::{Add, Div, Mul, Sub},
4    str::FromStr,
5};
6
7pub use error::*;
8
9mod error;
10mod tests;
11
12#[cfg(feature = "chrono")]
13mod chrono;
14#[cfg(feature = "clap")]
15mod clap;
16#[cfg(feature = "num-traits")]
17mod num_traits;
18#[cfg(feature = "schemars")]
19mod schemars;
20#[cfg(feature = "serde")]
21mod serde;
22#[cfg(feature = "time")]
23mod time;
24
25/// 1 kilohertz (kHz) in hertz
26pub const KILOHERTZ: u64 = 1_000;
27
28/// 1 megahertz (MHz) in hertz
29pub const MEGAHERTZ: u64 = 1_000_000;
30
31/// 1 gigahertz (GHz) in hertz
32pub const GIGAHERTZ: u64 = 1_000_000_000;
33
34/// Represents a frequency
35///
36/// This struct is a wrapper around a `u64` value representing the frequency in hertz.
37/// It provides methods to convert between different frequency units (Hz, kHz, MHz, GHz) and
38/// to parse frequency strings.
39///
40/// # Units
41/// - Hertz (Hz)
42/// - Kilohertz (kHz)
43/// - Megahertz (MHz)
44/// - Gigahertz (GHz)
45///
46/// # Note
47/// When converting to a string or using display, the frequency is formatted with two decimal places.
48/// This is done to provide a consistent representation of the frequency. However, this may lead to
49/// precision loss when converting back to a number.
50///
51/// # Examples
52///
53/// ```rust
54/// use parse_frequency::Frequency;
55///
56/// let freq = Frequency::from_hz(parse_frequency::GIGAHERTZ);
57/// assert_eq!(freq.as_ghz(), 1);
58///
59/// let freq: Frequency = "2.5GHz".parse().unwrap();
60/// assert_eq!(freq.as_hz(), 2_500_000_000);
61///
62/// let strfreq: String = freq.to_string();
63/// assert_eq!(strfreq, "2.50 GHz");
64///
65/// println!("Frequency: {}", freq);
66/// ```
67#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
68#[repr(transparent)]
69pub struct Frequency(pub u64);
70
71unsafe impl Send for Frequency {}
72unsafe impl Sync for Frequency {}
73
74impl Frequency {
75    /// Equivalent to `0 Hz`
76    ///
77    /// ```rust
78    /// # use parse_frequency::Frequency;
79    /// assert_eq!(Frequency::ZERO, Frequency::from_hz(0));
80    /// ```
81    pub const ZERO: Self = Self(0);
82
83    /// Equivalent to `1 Hz`
84    ///
85    /// ```rust
86    /// # use parse_frequency::Frequency;
87    /// assert_eq!(Frequency::HERTZ, Frequency::from_hz(1));
88    /// ```
89    pub const HERTZ: Self = Self(1);
90
91    /// Equivalent to `1 kHz`
92    ///
93    /// ```rust
94    /// # use parse_frequency::Frequency;
95    /// assert_eq!(Frequency::KILOHERTZ, Frequency::from_khz(1));
96    /// ```
97    pub const KILOHERTZ: Self = Self(KILOHERTZ);
98
99    /// Equivalent to `1 MHz`
100    ///
101    /// ```rust
102    /// # use parse_frequency::Frequency;
103    /// assert_eq!(Frequency::MEGAHERTZ, Frequency::from_mhz(1));
104    /// ```
105    pub const MEGAHERTZ: Self = Self(MEGAHERTZ);
106
107    /// Equivalent to `1 GHz`
108    ///
109    /// ```rust
110    /// # use parse_frequency::Frequency;
111    /// assert_eq!(Frequency::GIGAHERTZ, Frequency::from_ghz(1));
112    /// ```
113    pub const GIGAHERTZ: Self = Self(GIGAHERTZ);
114
115    #[must_use]
116    #[doc(alias = "from_hertz")]
117    pub fn from_hz(hz: u64) -> Self {
118        Self(hz)
119    }
120
121    #[must_use]
122    #[doc(alias = "from_kilohertz")]
123    pub fn from_khz(khz: u64) -> Self {
124        Self(khz * KILOHERTZ)
125    }
126
127    #[must_use]
128    #[doc(alias = "from_megahertz")]
129    pub fn from_mhz(mhz: u64) -> Self {
130        Self(mhz * MEGAHERTZ)
131    }
132
133    #[must_use]
134    #[doc(alias = "from_gigahertz")]
135    pub fn from_ghz(ghz: u64) -> Self {
136        Self(ghz * GIGAHERTZ)
137    }
138
139    #[must_use]
140    #[doc(alias = "as_hertz")]
141    pub fn as_hz(&self) -> u64 {
142        self.0
143    }
144
145    #[must_use]
146    #[doc(alias = "as_kilohertz")]
147    pub fn as_khz(&self) -> u64 {
148        self.as_hz() / KILOHERTZ
149    }
150
151    #[must_use]
152    #[doc(alias = "as_megahertz")]
153    pub fn as_mhz(&self) -> u64 {
154        self.as_hz() / MEGAHERTZ
155    }
156
157    #[must_use]
158    #[doc(alias = "as_gigahertz")]
159    pub fn as_ghz(&self) -> u64 {
160        self.as_hz() / GIGAHERTZ
161    }
162
163    /// Converts the frequency to a `std::time::Duration`.
164    ///
165    /// # Examples
166    ///
167    /// ```rust
168    /// use parse_frequency::Frequency;
169    ///
170    /// let freq = Frequency::from_ghz(1);
171    /// let duration = freq.as_duration();
172    /// assert_eq!(duration.as_nanos(), 1);
173    /// ```
174    /// # Returns
175    /// A `std::time::Duration` representing the frequency.
176    #[must_use]
177    pub fn as_duration(&self) -> std::time::Duration {
178        if self.0 == 0 {
179            std::time::Duration::ZERO
180        } else {
181            std::time::Duration::from_nanos(GIGAHERTZ / self.0)
182        }
183    }
184}
185
186impl Display for Frequency {
187    // Precision loss is acceptable here
188    #[allow(clippy::cast_precision_loss)]
189    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190        let value = self.as_hz();
191
192        if value >= GIGAHERTZ {
193            write!(f, "{:.2} GHz", value as f64 / GIGAHERTZ as f64)
194        } else if value >= MEGAHERTZ {
195            write!(f, "{:.2} MHz", value as f64 / MEGAHERTZ as f64)
196        } else if value >= KILOHERTZ {
197            write!(f, "{:.2} kHz", value as f64 / KILOHERTZ as f64)
198        } else {
199            write!(f, "{value} Hz")
200        }
201    }
202}
203
204impl FromStr for Frequency {
205    type Err = Error;
206
207    fn from_str(s: &str) -> Result<Self> {
208        parse_frequency(s)
209    }
210}
211
212impl TryFrom<&str> for Frequency {
213    type Error = Error;
214
215    fn try_from(s: &str) -> Result<Self> {
216        parse_frequency(s)
217    }
218}
219
220impl TryFrom<String> for Frequency {
221    type Error = Error;
222
223    fn try_from(s: String) -> Result<Self> {
224        parse_frequency(&s)
225    }
226}
227
228impl Add for Frequency {
229    type Output = Self;
230
231    fn add(self, other: Self) -> Self::Output {
232        Self(self.0 + other.0)
233    }
234}
235
236impl Sub for Frequency {
237    type Output = Self;
238
239    fn sub(self, other: Self) -> Self::Output {
240        Self(self.0 - other.0)
241    }
242}
243
244impl Mul<u64> for Frequency {
245    type Output = Self;
246
247    fn mul(self, rhs: u64) -> Self::Output {
248        Self(self.0 * rhs)
249    }
250}
251
252impl Div<u64> for Frequency {
253    type Output = Self;
254
255    fn div(self, rhs: u64) -> Self::Output {
256        Self(self.0 / rhs)
257    }
258}
259
260/// Parses a frequency string and returns a `Frequency` instance.
261///
262/// # Examples
263///
264/// ```
265/// let freq = parse_frequency::parse_frequency("2.5GHz").unwrap();
266/// assert_eq!(freq.as_hz(), 2_500_000_000);
267///
268/// let freq = parse_frequency::parse_frequency("1.5MHz").unwrap();
269/// assert_eq!(freq.as_hz(), 1_500_000);
270///
271/// let freq = parse_frequency::parse_frequency("500kHz").unwrap();
272/// assert_eq!(freq.as_hz(), 500_000);
273///
274/// let freq = parse_frequency::parse_frequency("100Hz").unwrap();
275/// assert_eq!(freq.as_hz(), 100);
276///
277/// let freq = parse_frequency::parse_frequency("invalid").unwrap_err();
278/// assert_eq!(freq.to_string(), "Unknown unit: invalid");
279/// ```
280///
281/// # Errors
282///
283/// If the input string does not match any of the expected formats (e.g., "1GHz", "2.5MHz", etc.), an error is returned.
284pub fn parse_frequency(s: &str) -> Result<Frequency> {
285    let s = s.trim().to_lowercase();
286
287    let (value_str, multiplier) = if let Some(value) = s.strip_suffix("ghz") {
288        (value, 1_000_000_000)
289    } else if let Some(value) = s.strip_suffix("mhz") {
290        (value, 1_000_000)
291    } else if let Some(value) = s.strip_suffix("khz") {
292        (value, 1_000)
293    } else if let Some(value) = s.strip_suffix("hz") {
294        (value, 1)
295    } else {
296        return Err(Error::UnknownUnit(s.to_string()));
297    };
298
299    let value = value_str
300        .trim()
301        .parse::<f64>()
302        .map_err(|_| Error::InvalidValue(value_str.to_string()))?;
303
304    // Negative values are not allowed
305    if value.is_sign_negative() {
306        return Err(Error::InvalidValue(value_str.to_string()));
307    }
308
309    // It is OK to lose sign and precision here
310    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
311    let hz = (value * f64::from(multiplier)).round() as u64;
312    Ok(Frequency(hz))
313}