weather_utils/
temperature.rs1use approx::relative_eq;
2
3use core::cmp::Ordering;
4
5pub trait Temperature: Sized + Copy {
7 fn celsius(&self) -> Celsius;
9 fn fahrenheit(&self) -> Fahrenheit;
11 fn value(&self) -> f32;
13
14 fn from_celsius(celsius: Celsius) -> Self;
16}
17
18#[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#[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}