measurements/
temperature.rs1use super::measurement::*;
4#[cfg(feature = "from_str")]
5use regex::Regex;
6#[cfg(feature = "from_str")]
7use std::str::FromStr;
8
9#[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#[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 pub fn from_kelvin(kelvin_degrees: f64) -> Self {
49 TemperatureDelta { kelvin_degrees }
50 }
51
52 pub fn from_celsius(celsius_degrees: f64) -> Self {
54 TemperatureDelta::from_kelvin(celsius_degrees)
55 }
56
57 pub fn from_fahrenheit(fahrenheit_degrees: f64) -> Self {
59 TemperatureDelta {
60 kelvin_degrees: fahrenheit_degrees / 1.8,
61 }
62 }
63
64 pub fn from_rankine(rankine_degrees: f64) -> Self {
66 TemperatureDelta {
67 kelvin_degrees: rankine_degrees / 1.8,
68 }
69 }
70
71 pub fn as_kelvin(&self) -> f64 {
73 self.kelvin_degrees
74 }
75
76 pub fn as_celsius(&self) -> f64 {
78 self.kelvin_degrees
79 }
80
81 pub fn as_fahrenheit(&self) -> f64 {
83 self.kelvin_degrees * 1.8
84 }
85
86 pub fn as_rankine(&self) -> f64 {
88 self.kelvin_degrees * 1.8
89 }
90}
91
92impl Temperature {
93 pub fn from_kelvin(degrees_kelvin: f64) -> Self {
95 Temperature { degrees_kelvin }
96 }
97
98 pub fn from_celsius(degrees_celsius: f64) -> Self {
100 Self::from_kelvin(degrees_celsius + 273.15)
101 }
102
103 pub fn from_fahrenheit(degrees_fahrenheit: f64) -> Self {
105 Self::from_kelvin((degrees_fahrenheit - 32.0) / 1.8 + 273.15)
106 }
107
108 pub fn from_rankine(degrees_rankine: f64) -> Self {
110 Self::from_kelvin((degrees_rankine - 491.67) / 1.8 + 273.15)
111 }
112
113 pub fn as_kelvin(&self) -> f64 {
115 self.degrees_kelvin
116 }
117
118 pub fn as_celsius(&self) -> f64 {
120 self.degrees_kelvin - 273.15
121 }
122
123 pub fn as_fahrenheit(&self) -> f64 {
125 (self.degrees_kelvin - 273.15) * 1.8 + 32.0
126 }
127
128 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 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 #[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 #[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}