Skip to main content

weather_utils/
temperature.rs

1use approx::relative_eq;
2
3use core::cmp::Ordering;
4
5/// Trait defining the different ways to get a temperature.
6pub trait Temperature: Sized + Copy {
7    /// Get the temperature in degrees Celsius (°C).
8    fn celsius(&self) -> Celsius;
9    /// Get the temperature in degrees Fahrenheit (°F).
10    fn fahrenheit(&self) -> Fahrenheit;
11    /// Get the raw value.
12    fn value(&self) -> f32;
13
14    /// Create a Temperature from a Celsius temperature.
15    fn from_celsius(celsius: Celsius) -> Self;
16}
17
18/// The degrees Celsius temperature unit.
19#[derive(Clone, Copy, Debug, Default)]
20pub struct Celsius(pub f32);
21
22impl Temperature for Celsius {
23    fn celsius(&self) -> Celsius {
24        *self
25    }
26
27    fn fahrenheit(&self) -> Fahrenheit {
28        Fahrenheit(self.0 * 1.8 + 32.0)
29    }
30
31    fn value(&self) -> f32 {
32        self.0
33    }
34
35    fn from_celsius(celsius: Celsius) -> Self {
36        celsius
37    }
38}
39
40impl From<f32> for Celsius {
41    fn from(value: f32) -> Self {
42        Celsius(value)
43    }
44}
45
46impl From<Fahrenheit> for Celsius {
47    fn from(value: Fahrenheit) -> Self {
48        value.celsius()
49    }
50}
51
52impl PartialEq for Celsius {
53    fn eq(&self, other: &Self) -> bool {
54        relative_eq!(self.0, other.0, epsilon = 0.01)
55    }
56}
57
58impl PartialOrd for Celsius {
59    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
60        self.value().partial_cmp(&other.value())
61    }
62}
63
64/// The degrees Fahrenheit temperature unit.
65#[derive(Clone, Copy, Debug, Default)]
66pub struct Fahrenheit(pub f32);
67
68impl Temperature for Fahrenheit {
69    fn celsius(&self) -> Celsius {
70        Celsius((self.0 - 32.0) * 0.55555)
71    }
72
73    fn fahrenheit(&self) -> Fahrenheit {
74        *self
75    }
76
77    fn value(&self) -> f32 {
78        self.0
79    }
80
81    fn from_celsius(celsius: Celsius) -> Self {
82        celsius.fahrenheit()
83    }
84}
85
86impl From<f32> for Fahrenheit {
87    fn from(value: f32) -> Self {
88        Fahrenheit(value)
89    }
90}
91
92impl From<Celsius> for Fahrenheit {
93    fn from(value: Celsius) -> Self {
94        value.fahrenheit()
95    }
96}
97
98impl PartialEq for Fahrenheit {
99    fn eq(&self, other: &Self) -> bool {
100        relative_eq!(self.0, other.0, epsilon = 0.01)
101    }
102}
103
104impl PartialOrd for Fahrenheit {
105    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
106        self.value().partial_cmp(&other.value())
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use more_asserts::{assert_gt, assert_lt};
113    use rstest::rstest;
114
115    use super::*;
116
117    #[rstest]
118    #[case(0.0, Celsius(0.0))]
119    #[case(15.73, Celsius(15.73))]
120    #[case(-7.49, Celsius(-7.49))]
121    #[case(37.5, Celsius(37.5))]
122    fn test_celsius_from_f32(#[case] input: f32, #[case] expected_output: Celsius) {
123        let celsius: Celsius = input.into();
124        assert_eq!(celsius, expected_output);
125        assert_eq!(celsius.celsius(), celsius);
126    }
127
128    #[rstest]
129    #[case(32.0, Fahrenheit(32.0))]
130    #[case(60.31, Fahrenheit(60.31))]
131    #[case(18.52, Fahrenheit(18.52))]
132    #[case(99.5, Fahrenheit(99.5))]
133    fn test_fahrenheit_from_f32(#[case] input: f32, #[case] expected_output: Fahrenheit) {
134        let fahrenheit: Fahrenheit = input.into();
135        assert_eq!(fahrenheit, expected_output);
136        assert_eq!(fahrenheit.fahrenheit(), fahrenheit);
137    }
138
139    #[rstest]
140    #[case(Celsius(0.0), Fahrenheit(32.0))]
141    #[case(Celsius(15.73), Fahrenheit(60.31))]
142    #[case(Celsius(-7.49), Fahrenheit(18.52))]
143    #[case(Celsius(37.5), Fahrenheit(99.5))]
144    fn test_celcius_to_fahrenheit_conversion(
145        #[case] input: Celsius,
146        #[case] expected_output: Fahrenheit,
147    ) {
148        assert_eq!(input.fahrenheit(), expected_output);
149        let fahrenheit: Fahrenheit = input.into();
150        assert_eq!(fahrenheit, expected_output);
151    }
152
153    #[rstest]
154    #[case(Fahrenheit(32.0), Celsius(0.0))]
155    #[case(Fahrenheit(60.31), Celsius(15.73))]
156    #[case(Fahrenheit(18.52), Celsius(-7.49))]
157    #[case(Fahrenheit(99.5), Celsius(37.5))]
158    fn test_fahrenheit_to_celsius_conversion(
159        #[case] input: Fahrenheit,
160        #[case] expected_output: Celsius,
161    ) {
162        assert_eq!(input.celsius(), expected_output);
163        let celsius: Celsius = input.into();
164        assert_eq!(celsius, expected_output);
165    }
166
167    #[rstest]
168    #[case(Celsius(0.0), Celsius(0.001))]
169    #[case(Celsius(0.004), Celsius(0.0))]
170    #[case(Celsius(15.73), Celsius(15.728))]
171    fn test_celsius_eq(#[case] a: Celsius, #[case] b: Celsius) {
172        assert_eq!(a, b);
173    }
174
175    #[rstest]
176    #[case(Celsius(0.0), Celsius(10.3))]
177    #[case(Celsius(0.0), Celsius(0.09))]
178    #[case(Celsius(37.5), Celsius(38.9))]
179    fn test_celsius_ne(#[case] a: Celsius, #[case] b: Celsius) {
180        assert_ne!(a, b);
181    }
182
183    #[rstest]
184    #[case(Fahrenheit(32.0), Fahrenheit(32.001))]
185    #[case(Fahrenheit(32.004), Fahrenheit(32.0))]
186    #[case(Fahrenheit(60.31), Fahrenheit(60.308))]
187    fn test_fahrenheit_eq(#[case] a: Fahrenheit, #[case] b: Fahrenheit) {
188        assert_eq!(a, b);
189    }
190
191    #[rstest]
192    #[case(Fahrenheit(0.0), Fahrenheit(10.3))]
193    #[case(Fahrenheit(0.0), Fahrenheit(0.09))]
194    #[case(Fahrenheit(99.5), Fahrenheit(100.9))]
195    fn test_fahrenheit_ne(#[case] a: Fahrenheit, #[case] b: Fahrenheit) {
196        assert_ne!(a, b);
197    }
198
199    #[rstest]
200    #[case(Celsius(0.0), Celsius(-7.49))]
201    #[case(Celsius(15.73), Celsius(0.0))]
202    #[case(Celsius(37.5), Celsius(15.73))]
203    fn test_celsius_ord(#[case] a: Celsius, #[case] b: Celsius) {
204        assert_gt!(a, b);
205        assert_lt!(b, a);
206    }
207
208    #[rstest]
209    #[case(Fahrenheit(32.0), Fahrenheit(18.52))]
210    #[case(Fahrenheit(60.31), Fahrenheit(32.0))]
211    #[case(Fahrenheit(99.5), Fahrenheit(60.31))]
212    fn test_fahrenheit_ord(#[case] a: Fahrenheit, #[case] b: Fahrenheit) {
213        assert_gt!(a, b);
214        assert_lt!(b, a);
215    }
216}