weather_utils/
unit.rs

1use approx::relative_eq;
2
3/// Trait defining the different ways to get a temperature.
4pub trait TemperatureUnit {
5    /// Get the temperature in degrees Celsius (°C).
6    fn celsius(&self) -> f32;
7    /// Get the temperature in degrees Fahrenheit (°F).
8    fn fahrenheit(&self) -> f32;
9}
10
11/// The degrees Celsius temperature unit.
12#[derive(Clone, Copy, Debug, Default)]
13pub struct Celsius(pub f32);
14
15impl TemperatureUnit for Celsius {
16    fn celsius(&self) -> f32 {
17        self.0
18    }
19
20    fn fahrenheit(&self) -> f32 {
21        convert_celsius_to_fahrenheit(self.0)
22    }
23}
24
25impl From<Fahrenheit> for Celsius {
26    fn from(value: Fahrenheit) -> Self {
27        Self(convert_fahrenheit_to_celsius(value.0))
28    }
29}
30
31impl PartialEq for Celsius {
32    fn eq(&self, other: &Self) -> bool {
33        relative_eq!(self.0, other.0, epsilon = 0.01)
34    }
35}
36
37/// The degrees Fahrenheit temperature unit.
38#[derive(Clone, Copy, Debug, Default)]
39pub struct Fahrenheit(pub f32);
40
41impl TemperatureUnit for Fahrenheit {
42    fn celsius(&self) -> f32 {
43        convert_fahrenheit_to_celsius(self.0)
44    }
45
46    fn fahrenheit(&self) -> f32 {
47        self.0
48    }
49}
50
51impl From<Celsius> for Fahrenheit {
52    fn from(value: Celsius) -> Self {
53        Self(convert_celsius_to_fahrenheit(value.0))
54    }
55}
56
57impl PartialEq for Fahrenheit {
58    fn eq(&self, other: &Self) -> bool {
59        relative_eq!(self.0, other.0, epsilon = 0.01)
60    }
61}
62
63/// Converts a temperature in °C to °F.
64fn convert_celsius_to_fahrenheit(temperature: f32) -> f32 {
65    temperature * 1.8 + 32.0
66}
67
68/// Converts a temperature in °F to °C.
69fn convert_fahrenheit_to_celsius(temperature: f32) -> f32 {
70    (temperature - 32.0) * 0.55555
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76    use approx::assert_relative_eq;
77    use rstest::rstest;
78
79    #[rstest]
80    #[case(0.0, 32.0)]
81    #[case(15.73, 60.31)]
82    #[case(-7.49, 18.52)]
83    #[case(37.5, 99.5)]
84    fn test_celcius_to_fahrenheit_conversion(#[case] input: f32, #[case] expected_output: f32) {
85        assert_relative_eq!(
86            convert_celsius_to_fahrenheit(input),
87            expected_output,
88            epsilon = 0.01
89        );
90    }
91
92    #[rstest]
93    #[case(32.0, 0.0)]
94    #[case(60.31, 15.73)]
95    #[case(18.52, -7.49)]
96    #[case(99.5, 37.5)]
97    fn test_fahrenheit_to_celsius_conversion(#[case] input: f32, #[case] expected_output: f32) {
98        assert_relative_eq!(
99            convert_fahrenheit_to_celsius(input),
100            expected_output,
101            epsilon = 0.01
102        )
103    }
104
105    #[rstest]
106    #[case(0.0, 32.0)]
107    #[case(15.73, 60.31)]
108    #[case(-7.49, 18.52)]
109    #[case(37.5, 99.5)]
110    fn test_celsius(#[case] celsius: f32, #[case] expected_fahrenheit: f32) {
111        let temperature = Celsius(celsius);
112        assert_relative_eq!(temperature.celsius(), celsius, epsilon = f32::EPSILON);
113        assert_relative_eq!(
114            temperature.fahrenheit(),
115            expected_fahrenheit,
116            epsilon = 0.01
117        );
118    }
119
120    #[rstest]
121    #[case(32.0, 0.0)]
122    #[case(60.31, 15.73)]
123    #[case(18.52, -7.49)]
124    #[case(99.5, 37.5)]
125    fn test_fahrenheit(#[case] fahrenheit: f32, #[case] expected_celsius: f32) {
126        let temperature = Fahrenheit(fahrenheit);
127        assert_relative_eq!(temperature.fahrenheit(), fahrenheit, epsilon = f32::EPSILON);
128        assert_relative_eq!(temperature.celsius(), expected_celsius, epsilon = 0.01);
129    }
130
131    #[rstest]
132    #[case(0.0, 0.001)]
133    #[case(0.004, 0.0)]
134    #[case(15.73, 15.728)]
135    fn test_celsius_eq(#[case] a: f32, #[case] b: f32) {
136        assert_eq!(Celsius(a), Celsius(b));
137    }
138
139    #[rstest]
140    #[case(0.0, 10.3)]
141    #[case(0.0, 0.09)]
142    #[case(37.5, 38.9)]
143    fn test_celsius_ne(#[case] a: f32, #[case] b: f32) {
144        assert_ne!(Celsius(a), Celsius(b))
145    }
146
147    #[rstest]
148    #[case(32.0, 32.001)]
149    #[case(32.004, 32.0)]
150    #[case(60.31, 60.308)]
151    fn test_fahrenheit_eq(#[case] a: f32, #[case] b: f32) {
152        assert_eq!(Fahrenheit(a), Fahrenheit(b));
153    }
154
155    #[rstest]
156    #[case(0.0, 10.3)]
157    #[case(0.0, 0.09)]
158    #[case(99.5, 100.9)]
159    fn test_fahrenheit_ne(#[case] a: f32, #[case] b: f32) {
160        assert_ne!(Fahrenheit(a), Fahrenheit(b))
161    }
162}