1use super::*;
17
18use chrono::Duration;
19
20const AIS_CHAR_BITS: usize = 6;
21
22pub(crate) fn make_fragment_key(
24 sentence_type: &str,
25 message_id: u64,
26 fragment_count: u8,
27 fragment_number: u8,
28 radio_channel_code: &str,
29) -> String {
30 format!(
31 "{},{},{},{},{}",
32 sentence_type, fragment_count, fragment_number, message_id, radio_channel_code
33 )
34}
35
36pub(crate) fn parse_payload(payload: &str) -> Result<BitVec, String> {
38 let mut bv = BitVec::<usize, LocalBits>::with_capacity(payload.len() * 6);
39 for c in payload.chars() {
40 let mut ci = (c as u8) - 48;
41 if ci > 40 {
42 ci -= 8;
43 }
44
45 for i in 0..6 {
47 bv.push(((ci >> (5 - i)) & 0x01) != 0);
48 }
49 }
50
51 Ok(bv)
52}
53
54pub(crate) fn pick_u64(bv: &BitVec, index: usize, len: usize) -> u64 {
56 let mut res = 0;
57 for pos in index..(index + len) {
58 if let Some(b) = bv.get(pos) {
59 res = (res << 1) | (*b as u64);
60 } else {
61 res <<= 1;
62 }
63 }
64 res
65}
66
67pub(crate) fn pick_i64(bv: &BitVec, index: usize, len: usize) -> i64 {
69 let mut res = 0;
70 for pos in index..(index + len) {
71 if let Some(b) = bv.get(pos) {
72 res = (res << 1) | (*b as u64);
73 } else {
74 res <<= 1;
75 }
76 }
77
78 let sign_bit = 1 << (len - 1);
79 if res & sign_bit != 0 {
80 ((res & (sign_bit - 1)) as i64) - (sign_bit as i64)
81 } else {
82 res as i64
83 }
84}
85
86pub(crate) fn pick_string(bv: &BitVec, index: usize, char_count: usize) -> String {
89 let mut res = String::with_capacity(char_count);
90 for i in 0..char_count {
91 match pick_u64(bv, index + i * AIS_CHAR_BITS, AIS_CHAR_BITS) as u32 {
95 0 => break,
96 ch if ch < 32 => res.push(core::char::from_u32(64 + ch).unwrap()),
97 ch if ch < 64 => res.push(core::char::from_u32(ch).unwrap()),
98 ch => unreachable!("6-bit AIS character expected but value {} encountered!", ch),
99 }
100 }
101
102 let trimmed_len = res.trim_end().len();
103 res.truncate(trimmed_len);
104 res
105}
106
107pub(crate) fn pick_eta(bv: &BitVec, index: usize) -> Result<Option<DateTime<Utc>>, ParseError> {
109 pick_eta_with_now(
110 bv,
111 index,
112 Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).single().unwrap(),
113 )
114}
115
116fn pick_eta_with_now(
119 bv: &BitVec,
120 index: usize,
121 now: DateTime<Utc>,
122) -> Result<Option<DateTime<Utc>>, ParseError> {
123 let mut month = pick_u64(bv, index, 4) as u32;
125 let mut day = pick_u64(bv, index + 4, 5) as u32;
126 let mut hour = pick_u64(bv, index + 4 + 5, 5) as u32;
127 let mut minute = pick_u64(bv, index + 4 + 5 + 5, 6) as u32;
128
129 if month == 0 && day == 0 && hour == 24 && minute == 60 {
131 return Ok(None);
132 }
133
134 if month == 0 {
136 month = now.month();
137 }
138 if day == 0 {
139 day = now.day();
140 }
141 if hour == 24 {
142 hour = 23;
143 minute = 59;
144 }
145 if minute == 60 {
146 minute = 59;
147 }
148
149 let res_this = parse_valid_utc(now.year(), month, day, hour, minute, 30, 0);
152 let res_next = parse_valid_utc(now.year() + 1, month, day, hour, minute, 30, 0);
153 if res_this.is_err() && res_next.is_err() {
154 match res_this {
156 Ok(_) => {
157 unreachable!("This should never be reached");
158 }
159 Err(e) => Err(e),
160 }
161 } else if res_this.is_err() {
162 Ok(Some(res_next.unwrap()))
164 } else if res_next.is_err() {
165 Ok(Some(res_this.unwrap()))
167 } else {
168 let this_year_eta = res_this.unwrap();
171 if now - Duration::days(180) <= this_year_eta {
172 Ok(Some(this_year_eta))
173 } else {
174 Ok(res_next.ok())
175 }
176 }
177}
178
179pub(crate) fn pick_number_field<T: core::str::FromStr>(
181 split: &[&str],
182 num: usize,
183) -> Result<Option<T>, String> {
184 split
185 .get(num)
186 .filter(|s| !s.is_empty())
187 .map(|s| {
188 s.parse()
189 .map_err(|_| format!("Failed to parse field {}: {}", num, s))
190 })
191 .transpose()
192}
193
194pub(crate) fn pick_hex_field<T: num_traits::Num>(
196 split: &[&str],
197 num: usize,
198) -> Result<Option<T>, String> {
199 split
200 .get(num)
201 .filter(|s| !s.is_empty())
202 .map(|s| {
203 T::from_str_radix(s, 16)
204 .map_err(|_| format!("Failed to parse hex field {}: {}", num, s))
205 })
206 .transpose()
207}
208
209pub(crate) fn pick_string_field(split: &[&str], num: usize) -> Option<String> {
211 let s = split.get(num).unwrap_or(&"");
212 if !s.is_empty() {
213 Some(s.to_string())
214 } else {
215 None
216 }
217}
218
219pub(crate) fn parse_hhmmss(hhmmss: &str, now: DateTime<Utc>) -> Result<DateTime<Utc>, ParseError> {
221 let (hour, minute, second) =
222 parse_time(hhmmss).map_err(|_| format!("Invalid time format: {}", hhmmss))?;
223 parse_valid_utc(now.year(), now.month(), now.day(), hour, minute, second, 0)
224}
225
226pub(crate) fn parse_yymmdd_hhmmss(yymmdd: &str, hhmmss: &str) -> Result<DateTime<Utc>, ParseError> {
228 let now = Utc.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap();
229 let century = (now.year() / 100) * 100;
230 let (day, month, year) =
231 parse_date(yymmdd).map_err(|_| format!("Invalid date format: {}", yymmdd))?;
232 let (hour, minute, second) =
233 parse_time(hhmmss).map_err(|_| format!("Invalid time format: {}", hhmmss))?;
234 parse_valid_utc(century + year, month, day, hour, minute, second, 0)
235}
236
237pub(crate) fn parse_hhmmss_ss(
239 hhmmss: &str,
240 date: DateTime<Utc>,
241) -> Result<DateTime<Utc>, ParseError> {
242 let (hour, minute, second, nano) = parse_time_with_fractions(hhmmss)
243 .map_err(|_| format!("Invalid time format: {}", hhmmss))?;
244 parse_valid_utc(
245 date.year(),
246 date.month(),
247 date.day(),
248 hour,
249 minute,
250 second,
251 nano,
252 )
253}
254
255pub(crate) fn pick_date_with_fields(
257 split: &[&str],
258 year_field: usize,
259 month_field: usize,
260 day_field: usize,
261 hour: u32,
262 minute: u32,
263 second: u32,
264 nanos: u32,
265) -> Result<DateTime<Utc>, ParseError> {
266 let year = split.get(year_field).unwrap_or(&"").parse::<i32>()?;
267 let month = split.get(month_field).unwrap_or(&"").parse::<u32>()?;
268 let day = split.get(day_field).unwrap_or(&"").parse::<u32>()?;
269 parse_valid_utc(year, month, day, hour, minute, second, nanos)
270}
271
272pub(crate) fn pick_timezone_with_fields(
274 split: &[&str],
275 hour_field: usize,
276 minute_field: usize,
277) -> Result<FixedOffset, ParseError> {
278 let hour = split.get(hour_field).unwrap_or(&"").parse::<i32>()?;
279 let minute = split.get(minute_field).unwrap_or(&"0").parse::<i32>()?;
280
281 if let Some(offset) = FixedOffset::east_opt(hour * 3600 + hour.signum() * minute * 60) {
282 Ok(offset)
283 } else {
284 Err(ParseError::InvalidSentence(format!(
285 "Time zone offset out of bounds: {}:{}",
286 hour, minute
287 )))
288 }
289}
290
291fn parse_date(yymmdd: &str) -> Result<(u32, u32, i32), ParseError> {
293 let day = pick_s2(yymmdd, 0).parse::<u32>()?;
294 let month = pick_s2(yymmdd, 2).parse::<u32>()?;
295 let year = pick_s2(yymmdd, 4).parse::<i32>()?;
296 Ok((day, month, year))
297}
298
299fn parse_time(hhmmss: &str) -> Result<(u32, u32, u32), ParseError> {
301 let hour = pick_s2(hhmmss, 0).parse::<u32>()?;
302 let minute = pick_s2(hhmmss, 2).parse::<u32>()?;
303 let second = pick_s2(hhmmss, 4).parse::<u32>()?;
304 Ok((hour, minute, second))
305}
306
307fn parse_time_with_fractions(hhmmss: &str) -> Result<(u32, u32, u32, u32), ParseError> {
309 let hour = pick_s2(hhmmss, 0).parse::<u32>()?;
310 let minute = pick_s2(hhmmss, 2).parse::<u32>()?;
311 let second = pick_s2(hhmmss, 4).parse::<u32>()?;
312 let nano = {
313 let nano_str = hhmmss.get(6..).unwrap_or(".0");
314 if !nano_str.is_empty() {
315 (nano_str.parse::<f64>()? * 1000000000.0).round() as u32
316 } else {
317 0
318 }
319 };
320 Ok((hour, minute, second, nano))
321}
322
323pub(crate) fn parse_ymdhs(
325 year: i32,
326 month: u32,
327 day: u32,
328 hour: u32,
329 min: u32,
330 sec: u32,
331) -> Result<DateTime<Utc>, ParseError> {
332 parse_valid_utc(year, month, day, hour, min, sec, 0)
333}
334
335pub fn parse_valid_utc(
337 year: i32,
338 month: u32,
339 day: u32,
340 hour: u32,
341 min: u32,
342 sec: u32,
343 nano: u32,
344) -> Result<DateTime<Utc>, ParseError> {
345 let opt_utc = Utc
346 .ymd_opt(year, month, day)
347 .and_hms_nano_opt(hour, min, sec, nano);
348 match opt_utc {
349 chrono::LocalResult::Single(valid_utc) | chrono::LocalResult::Ambiguous(valid_utc, _) => {
350 Ok(valid_utc)
351 }
352 chrono::LocalResult::None => Err(format!(
353 "Failed to parse Utc Date from y:{} m:{} d:{} h:{} m:{} s:{}",
354 year, month, day, hour, min, sec
355 )
356 .into()),
357 }
358}
359
360fn pick_s2(s: &str, i: usize) -> &str {
362 let end = i + 2;
363 s.get(i..end).unwrap_or("")
364}
365
366pub(crate) fn parse_latitude_ddmm_mmm(
371 lat_string: &str,
372 hemisphere: &str,
373) -> Result<Option<f64>, ParseError> {
374 if lat_string.is_empty() {
376 return Ok(None);
377 }
378
379 let byte_string = lat_string.as_bytes();
381 if !(byte_string.iter().take(4).all(|c| c.is_ascii_digit())
382 && byte_string.get(4) == Some(&b'.')
383 && byte_string
384 .get(5)
385 .map(|c| c.is_ascii_digit())
386 .unwrap_or(false))
387 {
388 return Err(format!("Failed to parse latitude (DDMM.MMM) from {}", lat_string).into());
389 }
390 let end = 5 + byte_string
391 .iter()
392 .skip(5)
393 .take_while(|c| c.is_ascii_digit())
394 .count();
395
396 let d = lat_string[0..2].parse::<f64>().unwrap_or(0.0);
398 let m = lat_string[2..end].parse::<f64>().unwrap_or(0.0);
399 let val = d + m / 60.0;
400 Ok(Some(match hemisphere {
401 "N" => val,
402 "S" => -val,
403 _ => val,
404 }))
405}
406
407pub(crate) fn parse_longitude_dddmm_mmm(
412 lon_string: &str,
413 hemisphere: &str,
414) -> Result<Option<f64>, String> {
415 if lon_string.is_empty() {
417 return Ok(None);
418 }
419
420 let byte_string = lon_string.as_bytes();
422 if !(byte_string.iter().take(5).all(|c| c.is_ascii_digit())
423 && byte_string.get(5) == Some(&b'.')
424 && byte_string
425 .get(6)
426 .map(|c| c.is_ascii_digit())
427 .unwrap_or(false))
428 {
429 return Err(format!(
430 "Failed to parse longitude (DDDMM.MMM) from {}",
431 lon_string
432 ));
433 }
434 let end = 6 + byte_string
435 .iter()
436 .skip(6)
437 .take_while(|c| c.is_ascii_digit())
438 .count();
439
440 let d = lon_string[0..3].parse::<f64>().unwrap_or(0.0);
442 let m = lon_string[3..end].parse::<f64>().unwrap_or(0.0);
443 let val = d + m / 60.0;
444 Ok(Some(match hemisphere {
445 "E" => val,
446 "W" => -val,
447 _ => val,
448 }))
449}
450
451pub(crate) fn parse_latitude_m_m(
456 lat_string: &str,
457 hemisphere: &str,
458) -> Result<Option<f64>, ParseError> {
459 if !lat_string.is_empty() {
460 match lat_string.parse::<f64>() {
461 Ok(lat) => match hemisphere {
462 "N" => Ok(Some(lat / 60.0)),
463 "S" => Ok(Some(-lat / 60.0)),
464 _ => Err(format!("Bad hemispehre: {}", hemisphere).into()),
465 },
466 Err(_) => Err(format!("Failed to parse float: {}", lat_string).into()),
467 }
468 } else {
469 Ok(None)
470 }
471}
472
473pub(crate) fn parse_longitude_m_m(
478 lon_string: &str,
479 hemisphere: &str,
480) -> Result<Option<f64>, String> {
481 if !lon_string.is_empty() {
482 match lon_string.parse::<f64>() {
483 Ok(lon) => match hemisphere {
484 "E" => Ok(Some(lon / 60.0)),
485 "W" => Ok(Some(-lon / 60.0)),
486 _ => Err(format!("Bad hemispehre: {}", hemisphere)),
487 },
488 Err(_) => Err(format!("Failed to parse float: {}", lon_string)),
489 }
490 } else {
491 Ok(None)
492 }
493}
494
495#[cfg(test)]
498mod test {
499 use super::*;
500
501 #[test]
502 fn test_parse_payload() {
503 match parse_payload("w7b0P1") {
504 Ok(bv) => {
505 assert_eq!(
506 bv,
507 bits![
508 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, ]
515 );
516 }
517 Err(e) => {
518 assert_eq!(e, "OK");
519 }
520 }
521 }
522
523 #[test]
524 fn test_pick_u64() {
525 let bv = bitvec![1, 0, 1, 1, 0, 1];
526 assert_eq!(pick_u64(&bv, 0, 2), 2);
527 assert_eq!(pick_u64(&bv, 2, 2), 3);
528 assert_eq!(pick_u64(&bv, 4, 2), 1);
529 assert_eq!(pick_u64(&bv, 0, 6), 45);
530 assert_eq!(pick_u64(&bv, 4, 4), 4);
531 assert_eq!(pick_u64(&bv, 6, 2), 0);
532 }
533
534 #[test]
535 fn test_pick_i64() {
536 assert_eq!(pick_i64(&bitvec![0, 1, 1, 1, 1, 1], 0, 6), 31);
537 assert_eq!(pick_i64(&bitvec![0, 0, 0, 0, 0, 1], 0, 6), 1);
538 assert_eq!(pick_i64(&bitvec![0, 0, 0, 0, 0, 0], 0, 6), 0);
539 assert_eq!(pick_i64(&bitvec![1, 1, 1, 1, 1, 1], 0, 6), -1);
540 assert_eq!(pick_i64(&bitvec![1, 0, 0, 0, 0, 0], 0, 6), -32);
541 }
542
543 #[test]
544 fn test_pick_string() {
545 let bv = bitvec![
546 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, ];
556 assert_eq!(pick_string(&bv, 0, bv.len() / 6), "?AG_4:!");
557 }
558
559 #[test]
560 fn test_pick_eta() {
561 let bv = bitvec![
563 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, ];
568 let eta = pick_eta(&bv, 0).ok().unwrap();
569 assert_eq!(
570 eta,
571 Utc.with_ymd_and_hms(eta.unwrap().year(), 10, 11, 22, 57, 30)
572 .single()
573 );
574
575 let bv = bitvec![
577 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, ];
582 assert!(!pick_eta(&bv, 0).is_ok());
583
584 let bv = bitvec![
586 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, ];
591 assert!(!pick_eta(&bv, 0).is_ok());
592
593 let bv = bitvec![
595 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, ];
600 assert!(!pick_eta(&bv, 0).is_ok());
601
602 let bv = bitvec![
604 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, ];
609 assert!(!pick_eta(&bv, 0).is_ok());
610 }
611
612 #[test]
613 fn test_pick_eta_with_now() {
614 let feb28 = bitvec![
616 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ];
621
622 let feb29 = bitvec![
624 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ];
629
630 let then = Utc
632 .with_ymd_and_hms(2020, 12, 31, 0, 0, 0)
633 .single()
634 .unwrap();
635 assert_eq!(
636 pick_eta_with_now(&feb29, 0, then).ok().unwrap(),
637 Utc.with_ymd_and_hms(2020, 2, 29, 0, 0, 30).single()
638 );
639
640 let then = Utc
642 .with_ymd_and_hms(2020, 12, 31, 0, 0, 0)
643 .single()
644 .unwrap();
645 assert_eq!(
646 pick_eta_with_now(&feb28, 0, then).ok().unwrap(),
647 Utc.with_ymd_and_hms(2021, 2, 28, 0, 0, 30).single()
648 );
649
650 let then = Utc
652 .with_ymd_and_hms(2021, 12, 31, 0, 0, 0)
653 .single()
654 .unwrap();
655 assert_eq!(pick_eta_with_now(&feb29, 0, then).is_ok(), false);
656
657 let then = Utc
659 .with_ymd_and_hms(2021, 12, 31, 0, 0, 0)
660 .single()
661 .unwrap();
662 assert_eq!(pick_eta_with_now(&feb28, 0, then).is_ok(), true);
663
664 let then = Utc.with_ymd_and_hms(2021, 3, 1, 0, 0, 0).single().unwrap();
666 assert_eq!(
667 pick_eta_with_now(&feb28, 0, then).ok().unwrap(),
668 Utc.with_ymd_and_hms(2021, 2, 28, 0, 0, 30).single()
669 );
670
671 let then = Utc.with_ymd_and_hms(2021, 8, 31, 0, 0, 0).single().unwrap();
673 assert_eq!(
674 pick_eta_with_now(&feb28, 0, then).ok().unwrap(),
675 Utc.with_ymd_and_hms(2022, 2, 28, 0, 0, 30).single()
676 );
677 }
678
679 #[test]
680 fn test_parse_valid_utc() {
681 assert!(parse_valid_utc(2020, 2, 29, 0, 0, 0, 0).is_ok());
682 assert!(!parse_valid_utc(2021, 2, 29, 0, 0, 0, 0).is_ok());
683 }
684
685 #[test]
686 fn test_pick_number_field() {
687 let s: Vec<&str> = "128,0,8.0,,xyz".split(',').collect();
688 assert_eq!(pick_number_field::<u8>(&s, 0).ok().unwrap().unwrap(), 128);
689 assert_eq!(pick_number_field::<u8>(&s, 1).ok().unwrap().unwrap(), 0);
690 assert_eq!(pick_number_field::<f64>(&s, 2).ok().unwrap().unwrap(), 8.0);
691 assert_eq!(pick_number_field::<u16>(&s, 3).ok().unwrap(), None);
692 assert!(!pick_number_field::<u32>(&s, 4).is_ok());
693 assert_eq!(pick_number_field::<u32>(&s, 5).ok().unwrap(), None);
694 }
695
696 #[test]
697 fn test_pick_hex_field() {
698 let s: Vec<&str> = "ff,0,,FFFF,8080808080808080".split(',').collect();
699 assert_eq!(pick_hex_field::<u8>(&s, 0).unwrap().unwrap(), 255);
700 assert_eq!(pick_hex_field::<u8>(&s, 1).unwrap().unwrap(), 0);
701 assert_eq!(pick_hex_field::<u8>(&s, 2).unwrap(), None);
702 assert_eq!(pick_hex_field::<u16>(&s, 3).unwrap().unwrap(), 65535);
703 assert_eq!(
704 pick_hex_field::<u64>(&s, 4).unwrap().unwrap(),
705 9259542123273814144
706 );
707 }
708
709 #[test]
710 fn test_parse_latitude_m_m() {
711 assert::close(
712 parse_latitude_m_m("3480", "N").ok().unwrap().unwrap_or(0.0),
713 58.0,
714 0.1,
715 );
716 assert::close(
717 parse_latitude_m_m("3480", "S").ok().unwrap().unwrap_or(0.0),
718 -58.0,
719 0.1,
720 );
721 assert!(!parse_latitude_m_m("3480", "X").is_ok());
722 assert!(!parse_latitude_m_m("ABCD", "N").is_ok());
723 assert!(parse_latitude_m_m("", "N").is_ok());
724 assert_eq!(parse_latitude_m_m("", "N").ok().unwrap(), None);
725 }
726
727 #[test]
728 fn test_parse_longitude_m_m() {
729 assert::close(
730 parse_longitude_m_m("1140", "E")
731 .ok()
732 .unwrap()
733 .unwrap_or(0.0),
734 19.0,
735 0.1,
736 );
737 assert::close(
738 parse_longitude_m_m("1140", "W")
739 .ok()
740 .unwrap()
741 .unwrap_or(0.0),
742 -19.0,
743 0.1,
744 );
745 assert!(!parse_longitude_m_m("1140", "X").is_ok());
746 assert!(!parse_longitude_m_m("ABCD", "E").is_ok());
747 assert!(parse_longitude_m_m("", "E").is_ok());
748 assert_eq!(parse_longitude_m_m("", "E").ok().unwrap(), None);
749 }
750
751 #[test]
752 fn test_pick_string_field() {
753 let s: Vec<&str> = "a,b,,dd,e".split(',').collect();
754 assert_eq!(pick_string_field(&s, 0), Some("a".into()));
755 assert_eq!(pick_string_field(&s, 1), Some("b".into()));
756 assert_eq!(pick_string_field(&s, 2), None);
757 assert_eq!(pick_string_field(&s, 3), Some("dd".into()));
758 assert_eq!(pick_string_field(&s, 4), Some("e".into()));
759 assert_eq!(pick_string_field(&s, 5), None);
760 }
761
762 #[test]
763 fn test_parse_time_with_fractions() {
764 assert_eq!(
765 parse_time_with_fractions("123456.987").unwrap_or((0, 0, 0, 0)),
766 (12, 34, 56, 987000000)
767 );
768 assert_eq!(
769 parse_time_with_fractions("123456").unwrap_or((0, 0, 0, 0)),
770 (12, 34, 56, 0)
771 );
772 }
773
774 #[test]
775 fn test_parse_hhmmss_ss() {
776 let then = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).single().unwrap();
778 assert_eq!(
779 parse_hhmmss_ss("123456.987", then).ok(),
780 Some(Utc.ymd(2000, 1, 1).and_hms_nano(12, 34, 56, 987000000))
781 );
782
783 let then = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).single().unwrap();
785 assert_eq!(
786 parse_hhmmss_ss("123456", then).ok(),
787 Some(Utc.ymd(2000, 1, 1).and_hms_nano(12, 34, 56, 0))
788 );
789
790 assert_eq!(parse_hhmmss_ss("123456@", then).ok(), None);
792 }
793
794 #[test]
795 fn test_pick_date_with_fields() {
796 let s: Vec<&str> = "$GPZDA,072914.00,31,05,2018,+02,00".split(',').collect();
797 assert_eq!(
798 pick_date_with_fields(&s, 4, 3, 2, 0, 0, 0, 0).ok(),
799 Utc.with_ymd_and_hms(2018, 5, 31, 0, 0, 0).single()
800 )
801 }
802
803 #[test]
804 fn test_pick_timezone_with_fields() {
805 let s: Vec<&str> = ",,,,,+4,30".split(',').collect();
807 assert_eq!(
808 pick_timezone_with_fields(&s, 5, 6).ok(),
809 Some(FixedOffset::east(4 * 3600 + 30 * 60))
810 );
811
812 let s: Vec<&str> = ",,,,,-4,30".split(',').collect();
814 assert_eq!(
815 pick_timezone_with_fields(&s, 5, 6).ok(),
816 FixedOffset::east_opt(-4 * 3600 - 30 * 60)
817 );
818
819 let s: Vec<&str> = ",,,,,+25,00".split(',').collect();
821 assert!(!pick_timezone_with_fields(&s, 5, 6).is_ok());
822 }
823}