aprs_parser/
timestamp.rs

1use bytes::parse_bytes;
2use std::convert::TryFrom;
3use std::io::Write;
4
5use DecodeError;
6use EncodeError;
7
8#[derive(Eq, PartialEq, Debug, Copy, Clone)]
9pub struct DhmTimestamp(u8, u8, u8);
10
11/// Day of month, Hour and Minute in UTC
12impl DhmTimestamp {
13    pub fn new(d: u8, h: u8, m: u8) -> Option<Self> {
14        // We could theoretically be more restrictive here
15        // Is (0, 24, 0) invalid or is it the same as
16        // (1, 0, 0)?
17        if d <= 99 && h <= 99 && m <= 99 {
18            Some(Self(d, h, m))
19        } else {
20            None
21        }
22    }
23}
24
25impl TryFrom<Timestamp> for DhmTimestamp {
26    type Error = ();
27
28    fn try_from(t: Timestamp) -> Result<Self, ()> {
29        if let Timestamp::DDHHMM(d, h, m) = t {
30            Ok(Self(d, h, m))
31        } else {
32            Err(())
33        }
34    }
35}
36
37#[derive(Eq, PartialEq, Debug, Clone)]
38pub enum Timestamp {
39    /// Day of month, Hour and Minute in UTC
40    DDHHMM(u8, u8, u8),
41    /// Hour, Minute and Second in UTC
42    HHMMSS(u8, u8, u8),
43    /// Unsupported timestamp format
44    Unsupported(Vec<u8>),
45}
46
47impl Timestamp {
48    /// Day of month, Hour and Minute in UTC
49    pub fn new_dhm(d: u8, h: u8, m: u8) -> Option<Self> {
50        if d <= 99 && h <= 99 && m <= 99 {
51            Some(Self::DDHHMM(d, h, m))
52        } else {
53            None
54        }
55    }
56
57    /// Hour, Minute and Second in UTC
58    pub fn new_hms(h: u8, m: u8, s: u8) -> Option<Self> {
59        if h <= 99 && m <= 99 && s <= 99 {
60            Some(Self::HHMMSS(h, m, s))
61        } else {
62            None
63        }
64    }
65
66    pub fn encode<W: Write>(&self, buf: &mut W) -> Result<(), EncodeError> {
67        match self {
68            Self::DDHHMM(d, h, m) => write!(buf, "{:02}{:02}{:02}z", d, h, m)?,
69            Self::HHMMSS(h, m, s) => write!(buf, "{:02}{:02}{:02}h", h, m, s)?,
70            Self::Unsupported(s) => buf.write_all(s)?,
71        };
72
73        Ok(())
74    }
75}
76
77impl TryFrom<&[u8]> for Timestamp {
78    type Error = DecodeError;
79
80    fn try_from(b: &[u8]) -> Result<Self, Self::Error> {
81        if b.len() != 7 {
82            return Err(DecodeError::InvalidTimestamp(b.to_owned()));
83        }
84
85        if b[6] == b'/' {
86            return Ok(Timestamp::Unsupported(b.to_owned()));
87        }
88
89        let one =
90            parse_bytes(&b[0..2]).ok_or_else(|| DecodeError::InvalidTimestamp(b.to_owned()))?;
91        let two =
92            parse_bytes(&b[2..4]).ok_or_else(|| DecodeError::InvalidTimestamp(b.to_owned()))?;
93        let three =
94            parse_bytes(&b[4..6]).ok_or_else(|| DecodeError::InvalidTimestamp(b.to_owned()))?;
95
96        Ok(match b[6] {
97            b'z' | b'Z' => Timestamp::DDHHMM(one, two, three),
98            b'h' | b'H' => Timestamp::HHMMSS(one, two, three),
99            _ => return Err(DecodeError::InvalidTimestamp(b.to_owned())),
100        })
101    }
102}
103
104impl From<DhmTimestamp> for Timestamp {
105    fn from(t: DhmTimestamp) -> Self {
106        Self::DDHHMM(t.0, t.1, t.2)
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn parse_ddhhmm() {
116        assert_eq!(
117            Timestamp::try_from(&b"123456z"[..]),
118            Ok(Timestamp::DDHHMM(12, 34, 56))
119        );
120
121        assert_eq!(
122            Timestamp::try_from(&b"123456Z"[..]),
123            Ok(Timestamp::DDHHMM(12, 34, 56))
124        );
125    }
126
127    #[test]
128    fn parse_hhmmss() {
129        assert_eq!(
130            Timestamp::try_from(&b"123456h"[..]),
131            Ok(Timestamp::HHMMSS(12, 34, 56))
132        );
133
134        assert_eq!(
135            Timestamp::try_from(&b"123456H"[..]),
136            Ok(Timestamp::HHMMSS(12, 34, 56))
137        );
138    }
139
140    #[test]
141    fn parse_local_time() {
142        assert_eq!(
143            Timestamp::try_from(&b"123456/"[..]),
144            Ok(Timestamp::Unsupported(b"123456/".to_vec()))
145        );
146    }
147
148    #[test]
149    fn invalid_timestamp() {
150        assert_eq!(
151            Timestamp::try_from(&b"1234567"[..]),
152            Err(DecodeError::InvalidTimestamp(b"1234567".to_vec()))
153        );
154    }
155
156    #[test]
157    fn invalid_timestamp2() {
158        assert_eq!(
159            Timestamp::try_from(&b"123a56z"[..]),
160            Err(DecodeError::InvalidTimestamp(b"123a56z".to_vec()))
161        );
162    }
163
164    #[test]
165    fn encode_ddhhmm() {
166        let mut buf = vec![];
167        Timestamp::DDHHMM(65, 43, 21).encode(&mut buf).unwrap();
168        assert_eq!(b"654321z"[..], buf);
169    }
170
171    #[test]
172    fn encode_hhmmss() {
173        let mut buf = vec![];
174        Timestamp::HHMMSS(65, 43, 21).encode(&mut buf).unwrap();
175        assert_eq!(b"654321h"[..], buf);
176    }
177
178    #[test]
179    fn encode_local_time() {
180        let mut buf = vec![];
181        Timestamp::Unsupported(b"135a67z".to_vec())
182            .encode(&mut buf)
183            .unwrap();
184        assert_eq!(b"135a67z"[..], buf);
185    }
186
187    #[test]
188    fn convert_dhm_timestamp_to_normal_timestamp() {
189        let timestamp: Timestamp = DhmTimestamp::new(12, 34, 56).unwrap().into();
190        assert_eq!(Timestamp::new_dhm(12, 34, 56).unwrap(), timestamp);
191    }
192
193    #[test]
194    fn convert_timestamp_to_dhm_timestamp_success() {
195        use std::convert::TryInto;
196
197        let timestamp = Timestamp::new_dhm(65, 43, 21).unwrap();
198        assert_eq!(
199            DhmTimestamp::new(65, 43, 21).unwrap(),
200            timestamp.try_into().unwrap()
201        );
202    }
203
204    #[test]
205    fn convert_timestamp_to_dhm_timestamp_failure() {
206        use std::convert::TryInto;
207
208        let timestamp = Timestamp::new_hms(65, 43, 21).unwrap();
209        let dhm: Result<DhmTimestamp, ()> = timestamp.try_into();
210        assert_eq!(Err(()), dhm);
211    }
212}