measurements/
temperature.rs

1//! Types and constants for handling temperature.
2
3use super::measurement::*;
4#[cfg(feature = "from_str")]
5use regex::Regex;
6#[cfg(feature = "from_str")]
7use std::str::FromStr;
8
9/// The `Temperature` struct can be used to deal with absolute temperatures in
10/// a common way.
11///
12/// # Example
13///
14/// ```
15/// use measurements::Temperature;
16///
17/// let boiling_water = Temperature::from_celsius(100.0);
18/// let fahrenheit = boiling_water.as_fahrenheit();
19/// println!("Boiling water measures at {} degrees fahrenheit.", fahrenheit);
20/// ```
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22#[derive(Copy, Clone, Debug, Default)]
23pub struct Temperature {
24    degrees_kelvin: f64,
25}
26
27/// The `TemperatureDelta` struct can be used to deal with differences between
28/// temperatures in a common way.
29///
30/// # Example
31///
32/// ```
33/// use measurements::{Temperature, TemperatureDelta};
34///
35/// let boiling_water = Temperature::from_celsius(100.0);
36/// let frozen_water = Temperature::from_celsius(0.0);
37/// let difference: TemperatureDelta = boiling_water - frozen_water;
38/// println!("Boiling water is {} above freezing.", difference);
39/// ```
40#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
41#[derive(Copy, Clone, Debug)]
42pub struct TemperatureDelta {
43    kelvin_degrees: f64,
44}
45
46impl TemperatureDelta {
47    /// Create a new TemperatureDelta from a floating point value in Kelvin
48    pub fn from_kelvin(kelvin_degrees: f64) -> Self {
49        TemperatureDelta { kelvin_degrees }
50    }
51
52    /// Create a new TemperatureDelta from a floating point value in Celsius
53    pub fn from_celsius(celsius_degrees: f64) -> Self {
54        TemperatureDelta::from_kelvin(celsius_degrees)
55    }
56
57    /// Create a new TemperatureDelta from a floating point value in Fahrenheit
58    pub fn from_fahrenheit(fahrenheit_degrees: f64) -> Self {
59        TemperatureDelta {
60            kelvin_degrees: fahrenheit_degrees / 1.8,
61        }
62    }
63
64    /// Create a new TemperatureDelta from a floating point value in Rankine
65    pub fn from_rankine(rankine_degrees: f64) -> Self {
66        TemperatureDelta {
67            kelvin_degrees: rankine_degrees / 1.8,
68        }
69    }
70
71    /// Convert this TemperatureDelta to a floating point value in Kelvin
72    pub fn as_kelvin(&self) -> f64 {
73        self.kelvin_degrees
74    }
75
76    /// Convert this TemperatureDelta to a floating point value in Celsius
77    pub fn as_celsius(&self) -> f64 {
78        self.kelvin_degrees
79    }
80
81    /// Convert this TemperatureDelta to a floating point value in Fahrenheit
82    pub fn as_fahrenheit(&self) -> f64 {
83        self.kelvin_degrees * 1.8
84    }
85
86    /// Convert this TemperatureDelta to a floating point value in Rankine
87    pub fn as_rankine(&self) -> f64 {
88        self.kelvin_degrees * 1.8
89    }
90}
91
92impl Temperature {
93    /// Create a new Temperature from a floating point value in Kelvin
94    pub fn from_kelvin(degrees_kelvin: f64) -> Self {
95        Temperature { degrees_kelvin }
96    }
97
98    /// Create a new Temperature from a floating point value in Celsius
99    pub fn from_celsius(degrees_celsius: f64) -> Self {
100        Self::from_kelvin(degrees_celsius + 273.15)
101    }
102
103    /// Create a new Temperature from a floating point value in Fahrenheit
104    pub fn from_fahrenheit(degrees_fahrenheit: f64) -> Self {
105        Self::from_kelvin((degrees_fahrenheit - 32.0) / 1.8 + 273.15)
106    }
107
108    /// Create a new Temperature from a floating point value in Rankine
109    pub fn from_rankine(degrees_rankine: f64) -> Self {
110        Self::from_kelvin((degrees_rankine - 491.67) / 1.8 + 273.15)
111    }
112
113    /// Convert this absolute Temperature to a floating point value in Kelvin
114    pub fn as_kelvin(&self) -> f64 {
115        self.degrees_kelvin
116    }
117
118    /// Convert this absolute Temperature to a floating point value in Celsius
119    pub fn as_celsius(&self) -> f64 {
120        self.degrees_kelvin - 273.15
121    }
122
123    /// Convert this absolute Temperature to a floating point value in Fahrenheit
124    pub fn as_fahrenheit(&self) -> f64 {
125        (self.degrees_kelvin - 273.15) * 1.8 + 32.0
126    }
127
128    /// Convert this absolute Temperature to a floating point value in Rankine
129    pub fn as_rankine(&self) -> f64 {
130        (self.degrees_kelvin - 273.15) * 1.8 + 491.67
131    }
132}
133
134impl Measurement for Temperature {
135    fn get_base_units_name(&self) -> &'static str {
136        "K"
137    }
138
139    fn as_base_units(&self) -> f64 {
140        self.degrees_kelvin
141    }
142
143    fn from_base_units(degrees_kelvin: f64) -> Self {
144        Self::from_kelvin(degrees_kelvin)
145    }
146}
147
148impl Measurement for TemperatureDelta {
149    fn get_base_units_name(&self) -> &'static str {
150        "K"
151    }
152
153    fn as_base_units(&self) -> f64 {
154        self.kelvin_degrees
155    }
156
157    fn from_base_units(kelvin_degrees: f64) -> Self {
158        Self::from_kelvin(kelvin_degrees)
159    }
160}
161
162impl ::core::ops::Add<TemperatureDelta> for Temperature {
163    type Output = Temperature;
164
165    fn add(self, other: TemperatureDelta) -> Temperature {
166        Temperature::from_kelvin(self.degrees_kelvin + other.kelvin_degrees)
167    }
168}
169
170impl ::core::ops::Add<Temperature> for TemperatureDelta {
171    type Output = Temperature;
172
173    fn add(self, other: Temperature) -> Temperature {
174        other + self
175    }
176}
177
178impl ::core::ops::Sub<TemperatureDelta> for Temperature {
179    type Output = Temperature;
180
181    fn sub(self, other: TemperatureDelta) -> Temperature {
182        Temperature::from_kelvin(self.degrees_kelvin - other.kelvin_degrees)
183    }
184}
185
186impl ::core::ops::Sub<Temperature> for Temperature {
187    type Output = TemperatureDelta;
188
189    fn sub(self, other: Temperature) -> TemperatureDelta {
190        TemperatureDelta::from_kelvin(self.degrees_kelvin - other.degrees_kelvin)
191    }
192}
193
194impl ::core::cmp::Eq for Temperature {}
195impl ::core::cmp::PartialEq for Temperature {
196    fn eq(&self, other: &Self) -> bool {
197        self.as_base_units() == other.as_base_units()
198    }
199}
200
201impl ::core::cmp::PartialOrd for Temperature {
202    fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
203        self.as_base_units().partial_cmp(&other.as_base_units())
204    }
205}
206
207#[cfg(feature = "from_str")]
208impl FromStr for Temperature {
209    type Err = std::num::ParseFloatError;
210
211    /// Create a new Temperature from a string
212    /// Plain numbers in string are considered to be Celsius
213    fn from_str(val: &str) -> Result<Self, Self::Err> {
214        if val.is_empty() {
215            return Ok(Temperature::from_celsius(0.0));
216        }
217
218        let re = Regex::new(r"\s*([0-9.]*)\s?(deg|\u{00B0}|)?\s?([fckrFCKR]{1})\s*$").unwrap();
219        if let Some(caps) = re.captures(val) {
220            let float_val = caps.get(1).unwrap().as_str();
221            return Ok(
222                match caps.get(3).unwrap().as_str().to_uppercase().as_str() {
223                    "F" => Temperature::from_fahrenheit(float_val.parse::<f64>()?),
224                    "C" => Temperature::from_celsius(float_val.parse::<f64>()?),
225                    "K" => Temperature::from_kelvin(float_val.parse::<f64>()?),
226                    "R" => Temperature::from_rankine(float_val.parse::<f64>()?),
227                    _ => Temperature::from_celsius(val.parse::<f64>()?),
228                },
229            );
230        }
231
232        Ok(Temperature::from_celsius(val.parse::<f64>()?))
233    }
234}
235
236implement_display!(Temperature);
237implement_measurement!(TemperatureDelta);
238
239#[cfg(test)]
240mod test {
241    use crate::{temperature::*, test_utils::assert_almost_eq};
242
243    // Temperature Units
244    #[test]
245    fn kelvin() {
246        let t = Temperature::from_kelvin(100.0);
247        let o = t.as_kelvin();
248
249        assert_almost_eq(o, 100.0);
250    }
251
252    #[test]
253    fn celsius() {
254        let t = Temperature::from_kelvin(100.0);
255        let o = t.as_celsius();
256
257        assert_almost_eq(o, -173.15);
258    }
259
260    #[test]
261    fn fahrenheit() {
262        let t = Temperature::from_kelvin(100.0);
263        let o = t.as_fahrenheit();
264
265        assert_almost_eq(o, -279.67);
266    }
267
268    #[test]
269    fn rankine() {
270        let t = Temperature::from_kelvin(100.0);
271        let o = t.as_rankine();
272
273        assert_almost_eq(o, 180.0);
274    }
275
276    #[test]
277    #[cfg(feature = "from_str")]
278    fn empty_str() {
279        let t = Temperature::from_str("");
280        assert!(t.is_ok());
281
282        let o = t.unwrap().as_celsius();
283        assert_eq!(o, 0.0);
284    }
285
286    #[test]
287    #[cfg(feature = "from_str")]
288    fn celsius_str() {
289        let t = Temperature::from_str("100C");
290        assert!(t.is_ok());
291
292        let o = t.unwrap().as_celsius();
293        assert_almost_eq(o, 100.0);
294    }
295
296    #[test]
297    #[cfg(feature = "from_str")]
298    fn celsius_space_str() {
299        let t = Temperature::from_str("100 C");
300        assert!(t.is_ok());
301
302        let o = t.unwrap().as_celsius();
303        assert_almost_eq(o, 100.0);
304    }
305
306    #[test]
307    #[cfg(feature = "from_str")]
308    fn celsius_degree_str() {
309        let t = Temperature::from_str("100°C");
310        assert!(t.is_ok());
311
312        let o = t.unwrap().as_celsius();
313        assert_almost_eq(o, 100.0);
314    }
315
316    #[test]
317    #[cfg(feature = "from_str")]
318    fn fahrenheit_str() {
319        let t = Temperature::from_str("100F");
320        assert!(t.is_ok());
321
322        let o = t.unwrap().as_fahrenheit();
323        assert_almost_eq(o, 100.0);
324    }
325
326    #[test]
327    #[cfg(feature = "from_str")]
328    fn fahrenheit_lc_str() {
329        let t = Temperature::from_str("100 f");
330        assert!(t.is_ok());
331
332        let o = t.unwrap().as_fahrenheit();
333        assert_almost_eq(o, 100.0);
334    }
335
336    #[test]
337    #[cfg(feature = "from_str")]
338    fn fahrenheit_degree_str() {
339        let t = Temperature::from_str("100 deg f");
340        assert!(t.is_ok());
341
342        let o = t.unwrap().as_fahrenheit();
343        assert_almost_eq(o, 100.0);
344    }
345
346    #[test]
347    #[cfg(feature = "from_str")]
348    fn rankine_str() {
349        let t = Temperature::from_str("100R");
350        assert!(t.is_ok());
351
352        let o = t.unwrap().as_rankine();
353        assert_almost_eq(o, 100.0);
354    }
355
356    #[test]
357    #[cfg(feature = "from_str")]
358    fn rankine_degree_str() {
359        let t = Temperature::from_str("100 °R");
360        assert!(t.is_ok());
361
362        let o = t.unwrap().as_rankine();
363        assert_almost_eq(o, 100.0);
364    }
365
366    #[test]
367    #[cfg(feature = "from_str")]
368    fn number_str() {
369        let t = Temperature::from_str("100.5");
370        assert!(t.is_ok());
371
372        let o = t.unwrap().as_celsius();
373        assert_almost_eq(o, 100.5);
374    }
375
376    #[test]
377    #[cfg(feature = "from_str")]
378    fn invalid_str() {
379        let t = Temperature::from_str("abcd");
380        assert!(t.is_err());
381    }
382
383    // Traits
384    #[test]
385    fn add() {
386        let a = Temperature::from_kelvin(2.0);
387        let b = TemperatureDelta::from_kelvin(4.0);
388        let c = a + b;
389        let d = b + a;
390        assert_almost_eq(c.as_kelvin(), 6.0);
391        assert_eq!(c, d);
392    }
393
394    #[test]
395    fn add2() {
396        let a = TemperatureDelta::from_kelvin(2.0);
397        let b = TemperatureDelta::from_kelvin(4.0);
398        let c = a + b;
399        let d = b + a;
400        assert_almost_eq(c.as_kelvin(), 6.0);
401        assert_eq!(c, d);
402    }
403
404    #[test]
405    fn sub() {
406        let a = Temperature::from_kelvin(4.0);
407        let b = TemperatureDelta::from_kelvin(2.0);
408        let c = a - b;
409        assert_almost_eq(c.as_kelvin(), 2.0);
410    }
411
412    #[test]
413    fn sub2() {
414        let a = Temperature::from_fahrenheit(212.0);
415        let b = Temperature::from_celsius(75.0);
416        let c = a - b;
417        assert_almost_eq(c.as_kelvin(), 25.0);
418    }
419
420    #[test]
421    fn sub3() {
422        let a = TemperatureDelta::from_fahrenheit(180.0);
423        let b = TemperatureDelta::from_celsius(75.0);
424        let c = a - b;
425        assert_almost_eq(c.as_kelvin(), 25.0);
426    }
427
428    #[test]
429    fn mul() {
430        let a = TemperatureDelta::from_celsius(5.0);
431        let b = a * 2.0;
432        let c = 2.0 * a;
433        assert_almost_eq(b.as_celsius(), 10.0);
434        assert_eq!(b, c);
435    }
436
437    #[test]
438    fn eq() {
439        let a = Temperature::from_kelvin(2.0);
440        let b = Temperature::from_kelvin(2.0);
441        assert_eq!(a == b, true);
442    }
443
444    #[test]
445    fn neq() {
446        let a = Temperature::from_kelvin(2.0);
447        let b = Temperature::from_kelvin(4.0);
448        assert_eq!(a == b, false);
449    }
450
451    #[test]
452    fn cmp() {
453        let a = Temperature::from_kelvin(2.0);
454        let b = Temperature::from_kelvin(4.0);
455        assert_eq!(a < b, true);
456        assert_eq!(a <= b, true);
457        assert_eq!(a > b, false);
458        assert_eq!(a >= b, false);
459    }
460}