uls_core/records/
common.rs1use chrono::NaiveDate;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
8pub struct Coordinates {
9 pub lat_degrees: Option<i32>,
10 pub lat_minutes: Option<i32>,
11 pub lat_seconds: Option<f64>,
12 pub lat_direction: Option<char>,
13 pub long_degrees: Option<i32>,
14 pub long_minutes: Option<i32>,
15 pub long_seconds: Option<f64>,
16 pub long_direction: Option<char>,
17}
18
19impl Coordinates {
20 pub fn empty() -> Self {
22 Self {
23 lat_degrees: None,
24 lat_minutes: None,
25 lat_seconds: None,
26 lat_direction: None,
27 long_degrees: None,
28 long_minutes: None,
29 long_seconds: None,
30 long_direction: None,
31 }
32 }
33
34 pub fn is_empty(&self) -> bool {
36 self.lat_degrees.is_none()
37 && self.lat_minutes.is_none()
38 && self.lat_seconds.is_none()
39 && self.long_degrees.is_none()
40 && self.long_minutes.is_none()
41 && self.long_seconds.is_none()
42 }
43
44 pub fn to_decimal(&self) -> Option<(f64, f64)> {
46 let lat_deg = self.lat_degrees?;
47 let lat_min = self.lat_minutes.unwrap_or(0);
48 let lat_sec = self.lat_seconds.unwrap_or(0.0);
49 let lat_dir = self.lat_direction.unwrap_or('N');
50
51 let long_deg = self.long_degrees?;
52 let long_min = self.long_minutes.unwrap_or(0);
53 let long_sec = self.long_seconds.unwrap_or(0.0);
54 let long_dir = self.long_direction.unwrap_or('W');
55
56 let mut lat = lat_deg as f64 + (lat_min as f64 / 60.0) + (lat_sec / 3600.0);
57 let mut long = long_deg as f64 + (long_min as f64 / 60.0) + (long_sec / 3600.0);
58
59 if lat_dir == 'S' {
60 lat = -lat;
61 }
62 if long_dir == 'W' {
63 long = -long;
64 }
65
66 Some((lat, long))
67 }
68}
69
70impl Default for Coordinates {
71 fn default() -> Self {
72 Self::empty()
73 }
74}
75
76pub fn parse_uls_date(s: &str) -> Option<NaiveDate> {
78 let s = s.trim();
79 if s.is_empty() {
80 return None;
81 }
82
83 if let Ok(date) = NaiveDate::parse_from_str(s, "%m/%d/%Y") {
85 return Some(date);
86 }
87
88 if let Ok(date) = NaiveDate::parse_from_str(s, "%Y-%m-%d") {
90 return Some(date);
91 }
92
93 None
94}
95
96pub fn parse_opt_string(s: &str) -> Option<String> {
98 let s = s.trim();
99 if s.is_empty() {
100 None
101 } else {
102 Some(s.to_string())
103 }
104}
105
106pub fn parse_opt_i32(s: &str) -> Option<i32> {
108 let s = s.trim();
109 if s.is_empty() {
110 None
111 } else {
112 s.parse().ok()
113 }
114}
115
116pub fn parse_opt_i64(s: &str) -> Option<i64> {
118 let s = s.trim();
119 if s.is_empty() {
120 None
121 } else {
122 s.parse().ok()
123 }
124}
125
126pub fn parse_opt_f64(s: &str) -> Option<f64> {
128 let s = s.trim();
129 if s.is_empty() {
130 None
131 } else {
132 s.parse().ok()
133 }
134}
135
136pub fn parse_opt_char(s: &str) -> Option<char> {
138 let s = s.trim();
139 if s.is_empty() {
140 None
141 } else {
142 s.chars().next()
143 }
144}
145
146pub fn parse_i64_or_default(s: &str) -> i64 {
148 parse_opt_i64(s).unwrap_or(0)
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn test_coordinates_decimal() {
157 let coords = Coordinates {
158 lat_degrees: Some(40),
159 lat_minutes: Some(30),
160 lat_seconds: Some(0.0),
161 lat_direction: Some('N'),
162 long_degrees: Some(74),
163 long_minutes: Some(0),
164 long_seconds: Some(0.0),
165 long_direction: Some('W'),
166 };
167
168 let (lat, long) = coords.to_decimal().unwrap();
169 assert!((lat - 40.5).abs() < 0.001);
170 assert!((long - (-74.0)).abs() < 0.001);
171 }
172
173 #[test]
174 fn test_coordinates_empty() {
175 let coords = Coordinates::empty();
176 assert!(coords.is_empty());
177 assert!(coords.to_decimal().is_none());
178 }
179
180 #[test]
181 fn test_parse_uls_date() {
182 assert_eq!(
183 parse_uls_date("01/15/2024"),
184 Some(NaiveDate::from_ymd_opt(2024, 1, 15).unwrap())
185 );
186 assert_eq!(
187 parse_uls_date("2024-01-15"),
188 Some(NaiveDate::from_ymd_opt(2024, 1, 15).unwrap())
189 );
190 assert_eq!(parse_uls_date(""), None);
191 assert_eq!(parse_uls_date("invalid"), None);
192 }
193
194 #[test]
195 fn test_parse_opt_string() {
196 assert_eq!(parse_opt_string("hello"), Some("hello".to_string()));
197 assert_eq!(parse_opt_string(" hello "), Some("hello".to_string()));
198 assert_eq!(parse_opt_string(""), None);
199 assert_eq!(parse_opt_string(" "), None);
200 }
201
202 #[test]
203 fn test_parse_opt_i32() {
204 assert_eq!(parse_opt_i32("123"), Some(123));
205 assert_eq!(parse_opt_i32("-456"), Some(-456));
206 assert_eq!(parse_opt_i32(""), None);
207 assert_eq!(parse_opt_i32("abc"), None);
208 }
209
210 #[test]
211 fn test_parse_opt_f64() {
212 assert_eq!(parse_opt_f64("123.456"), Some(123.456));
213 assert_eq!(parse_opt_f64("-0.5"), Some(-0.5));
214 assert_eq!(parse_opt_f64(""), None);
215 }
216}