1use std::fmt::Display;
2use std::str::FromStr;
3use num_enum::{IntoPrimitive, TryFromPrimitive};
4
5#[repr(i8)]
7#[derive(Debug, PartialEq, Copy, Clone, IntoPrimitive, TryFromPrimitive)]
8pub enum Unit
9{
10 Degree = 0,
12
13 Minute = 1,
15
16 Second = 2,
18}
19
20impl Unit
21{
22 pub fn abbr(self) -> &'static str
24 {
25 match self
26 {
27 Self::Degree => "°",
28 Self::Minute => "'",
29 Self::Second => "\"",
30 }
31 }
32
33 pub fn coefficient(self) -> f64
35 {
36 match self
37 {
38 Self::Degree => 1.0,
39 Self::Minute => 60.0,
40 Self::Second => 3600.0,
41 }
42 }
43
44 pub fn convert(value: f64, from: Unit, to: Unit) -> f64
46 {
47 value / from.coefficient() * to.coefficient()
48 }
49}
50
51impl Display for Unit
53{
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
55 {
56 write!(f, "{}", self.abbr())
57 }
58}
59
60impl FromStr for Unit
62{
63 type Err = ();
64
65 fn from_str(s: &str) -> Result<Self, Self::Err>
66 {
67 match s
68 {
69 "°" => Ok(Unit::Degree),
70 "'" => Ok(Unit::Minute),
71 "\"" => Ok(Unit::Second),
72 _ => Err(()),
73 }
74 }
75}
76
77#[cfg(test)]
78mod tests
79{
80 use super::*;
81 use rstest::*;
82 use float_cmp::assert_approx_eq;
83
84 #[rstest]
85 #[case(100.0, Unit::Degree)]
86 #[case(100.0, Unit::Minute)]
87 #[case(100.0, Unit::Second)]
88 fn test_unit_convert_same(#[case] value: f64, #[case] unit: Unit)
89 {
90 let actual = Unit::convert(value, unit, unit);
91 assert_approx_eq!(f64, value, actual);
92 }
93
94 #[rstest]
95 #[case(100.0, Unit::Degree, Unit::Minute, 6000.0)]
96 #[case(100.0, Unit::Degree, Unit::Second, 360000.0)]
97 #[case(100.0, Unit::Minute, Unit::Second, 6000.0)]
98 fn test_unit_convert_dms(#[case] value: f64, #[case] from: Unit, #[case] to: Unit, #[case] expected: f64)
99 {
100 let actual = Unit::convert(value, from, to);
101 assert_approx_eq!(f64, expected, actual);
102
103 let actual = Unit::convert(actual, to, from);
104 assert_approx_eq!(f64, value, actual);
105 }
106
107 #[rstest]
108 #[case(Unit::Degree, "°")]
109 #[case(Unit::Minute, "'")]
110 #[case(Unit::Second, "\"")]
111 fn test_unit_str(#[case] unit: Unit, #[case] text: String)
112 {
113 assert_eq!(text, unit.to_string());
114 assert_eq!(unit, Unit::from_str(text.as_str()).unwrap());
115 }
116
117 #[rstest]
118 #[case("")]
119 #[case("ABCD")]
120 fn test_unit_str_error(#[case] text: String)
121 {
122 let unit = Unit::from_str(text.as_str());
123 assert!(unit.is_err());
124 }
125}