email_parser/parsing/
time.rs

1use crate::prelude::*;
2
3pub fn day_name(input: &[u8]) -> Res<Day> {
4    if let (Some(input), Some(letters)) = (input.get(3..), input.get(..3)) {
5        let letters = letters.to_ascii_lowercase();
6        match letters.as_slice() {
7            b"mon" => Ok((input, Day::Monday)),
8            b"tue" => Ok((input, Day::Tuesday)),
9            b"wed" => Ok((input, Day::Wednesday)),
10            b"thu" => Ok((input, Day::Thursday)),
11            b"fri" => Ok((input, Day::Friday)),
12            b"sat" => Ok((input, Day::Saturday)),
13            b"sun" => Ok((input, Day::Sunday)),
14            _ => Err(Error::Unknown("Not a valid day_name")),
15        }
16    } else {
17        Err(Error::Unknown(
18            "Expected day_name, but characters are missing (at least 3).",
19        ))
20    }
21}
22
23pub fn month(input: &[u8]) -> Res<Month> {
24    if let (Some(input), Some(letters)) = (input.get(3..), input.get(..3)) {
25        let letters = letters.to_ascii_lowercase();
26        match letters.as_slice() {
27            b"jan" => Ok((input, Month::January)),
28            b"feb" => Ok((input, Month::February)),
29            b"mar" => Ok((input, Month::March)),
30            b"apr" => Ok((input, Month::April)),
31            b"may" => Ok((input, Month::May)),
32            b"jun" => Ok((input, Month::June)),
33            b"jul" => Ok((input, Month::July)),
34            b"aug" => Ok((input, Month::August)),
35            b"sep" => Ok((input, Month::September)),
36            b"oct" => Ok((input, Month::October)),
37            b"nov" => Ok((input, Month::November)),
38            b"dec" => Ok((input, Month::December)),
39            _ => Err(Error::Unknown("Not a valid month")),
40        }
41    } else {
42        Err(Error::Unknown(
43            "Expected month, but characters are missing (at least 3).",
44        ))
45    }
46}
47
48pub fn day_of_week(input: &[u8]) -> Res<Day> {
49    let (input, _fws) = optional(input, fws);
50    let (input, day) = day_name(input)?;
51    let (input, ()) = tag(input, b",")?;
52    Ok((input, day))
53}
54
55pub fn year(input: &[u8]) -> Res<usize> {
56    let (input, _) = fws(input)?;
57
58    let (input, year) =
59        take_while1(input, is_digit).map_err(|_e| Error::Unknown("no digit in year"))?;
60    if year.len() < 4 {
61        return Err(Error::Unknown("year is expected to have 4 digits or more"));
62    }
63    let year: usize = year
64        .parse()
65        .map_err(|_e| Error::Unknown("Failed to parse year"))?;
66
67    if year < 1990 {
68        return Err(Error::Unknown("year must be after 1990"));
69    }
70
71    let (input, _) = fws(input)?;
72
73    Ok((input, year))
74}
75
76pub fn day(input: &[u8]) -> Res<u8> {
77    let (input, _fws) = optional(input, fws);
78    let (mut input, mut day) = digit(input)?;
79    if let Ok((new_input, digit)) = digit(input) {
80        day *= 10;
81        day += digit;
82        input = new_input;
83    }
84    if day > 31 {
85        return Err(Error::Unknown("day must be less than 31"));
86    }
87    let (input, _) = fws(input)?;
88    Ok((input, day))
89}
90
91pub fn time_of_day(input: &[u8]) -> Res<Time> {
92    let (input, hour) = two_digits(input)?;
93    if hour > 23 {
94        return Err(Error::Unknown("There is only 24 hours in a day"));
95    }
96    let (input, ()) = tag(input, b":")?;
97
98    let (input, minute) = two_digits(input)?;
99    if minute > 59 {
100        return Err(Error::Unknown("There is only 60 minutes per hour"));
101    }
102
103    if input.starts_with(b":") {
104        let new_input = &input[1..];
105        if let Ok((new_input, second)) = two_digits(new_input) {
106            if second > 60 {
107                // leap second allowed
108                return Err(Error::Unknown("There is only 60 seconds in a minute"));
109            }
110            return Ok((
111                new_input,
112                Time {
113                    hour,
114                    minute,
115                    second,
116                },
117            ));
118        }
119    }
120
121    Ok((
122        input,
123        Time {
124            hour,
125            minute,
126            second: 0,
127        },
128    ))
129}
130
131pub fn zone(input: &[u8]) -> Res<Zone> {
132    let (mut input, _fws) = fws(input)?;
133
134    let sign = match input.get(0) {
135        Some(b'+') => true,
136        Some(b'-') => false,
137        None => return Err(Error::Unknown("Expected more characters in zone")),
138        _ => return Err(Error::Unknown("Invalid sign character in zone")),
139    };
140    input = &input[1..];
141
142    let (input, hour_offset) = two_digits(input)?;
143    let (input, minute_offset) = two_digits(input)?;
144
145    if minute_offset > 59 {
146        return Err(Error::Unknown("zone minute_offset out of range"));
147    }
148
149    Ok((
150        input,
151        Zone {
152            sign,
153            hour_offset,
154            minute_offset,
155        },
156    ))
157}
158
159pub fn time(input: &[u8]) -> Res<TimeWithZone> {
160    let (input, time) = time_of_day(input)?;
161    let (input, zone) = zone(input)?;
162    Ok((input, TimeWithZone { time, zone }))
163}
164
165pub fn date(input: &[u8]) -> Res<Date> {
166    let (input, day) = day(input)?;
167    let (input, month) = month(input)?;
168    let (input, year) = year(input)?;
169    Ok((input, Date { day, month, year }))
170}
171
172pub fn date_time(input: &[u8]) -> Res<DateTime> {
173    let (input, day) = optional(input, day_of_week);
174    let (input, date) = date(input)?;
175    let (input, time) = time(input)?;
176    let (input, _cfws) = optional(input, cfws);
177    Ok((
178        input,
179        DateTime {
180            day_name: day,
181            date,
182            time,
183        },
184    ))
185}
186
187#[cfg(test)]
188mod test {
189    use super::*;
190
191    #[test]
192    fn test_day() {
193        assert_eq!(day_name(b"Mon ").unwrap().1, Day::Monday);
194        assert_eq!(day_name(b"moN ").unwrap().1, Day::Monday);
195        assert_eq!(day_name(b"thu").unwrap().1, Day::Thursday);
196
197        assert_eq!(day_of_week(b"   thu, ").unwrap().1, Day::Thursday);
198        assert_eq!(day_of_week(b"wed, ").unwrap().1, Day::Wednesday);
199        assert_eq!(day_of_week(b" Sun,").unwrap().1, Day::Sunday);
200
201        assert_eq!(day(b"31 ").unwrap().1, 31);
202        assert_eq!(day(b"9 ").unwrap().1, 9);
203        assert_eq!(day(b"05 ").unwrap().1, 5);
204        assert_eq!(day(b"23 ").unwrap().1, 23);
205    }
206
207    #[test]
208    fn test_month_and_year() {
209        assert_eq!(month(b"Apr ").unwrap().1, Month::April);
210        assert_eq!(month(b"may ").unwrap().1, Month::May);
211        assert_eq!(month(b"deC ").unwrap().1, Month::December);
212
213        assert_eq!(year(b" 2020 ").unwrap().1, 2020);
214        assert_eq!(year(b"\r\n 1995 ").unwrap().1, 1995);
215        assert_eq!(year(b" 250032 ").unwrap().1, 250032);
216    }
217
218    #[test]
219    fn test_date() {
220        assert_eq!(
221            date(b"1 nov 2020 ").unwrap().1,
222            Date {
223                day: 1,
224                month: Month::November,
225                year: 2020
226            }
227        );
228        assert_eq!(
229            date(b"25 dec 2038 ").unwrap().1,
230            Date {
231                day: 25,
232                month: Month::December,
233                year: 2038
234            }
235        );
236
237        assert_eq!(
238            date_time(b"Mon, 12 Apr 2023 10:25:03 +0000").unwrap().1,
239            DateTime {
240                day_name: Some(Day::Monday),
241                date: Date {
242                    day: 12,
243                    month: Month::April,
244                    year: 2023
245                },
246                time: TimeWithZone {
247                    time: Time {
248                        hour: 10,
249                        minute: 25,
250                        second: 3
251                    },
252                    zone: Zone {
253                        sign: true,
254                        hour_offset: 0,
255                        minute_offset: 0
256                    }
257                },
258            }
259        );
260        assert_eq!(
261            date_time(b"5 May 2003 18:59:03 +0000").unwrap().1,
262            DateTime {
263                day_name: None,
264                date: Date {
265                    day: 5,
266                    month: Month::May,
267                    year: 2003
268                },
269                time: TimeWithZone {
270                    time: Time {
271                        hour: 18,
272                        minute: 59,
273                        second: 3
274                    },
275                    zone: Zone {
276                        sign: true,
277                        hour_offset: 0,
278                        minute_offset: 0
279                    }
280                }
281            }
282        );
283    }
284
285    #[test]
286    fn test_time() {
287        assert_eq!(
288            time_of_day(b"10:40:29").unwrap().1,
289            Time {
290                hour: 10,
291                minute: 40,
292                second: 29
293            }
294        );
295        assert_eq!(
296            time_of_day(b"10:40 ").unwrap().1,
297            Time {
298                hour: 10,
299                minute: 40,
300                second: 0
301            }
302        );
303        assert_eq!(
304            time_of_day(b"05:23 ").unwrap().1,
305            Time {
306                hour: 5,
307                minute: 23,
308                second: 0
309            }
310        );
311
312        assert_eq!(
313            zone(b" +1000 ").unwrap().1,
314            Zone {
315                sign: true,
316                hour_offset: 10,
317                minute_offset: 0
318            }
319        );
320        assert_eq!(
321            zone(b" -0523 ").unwrap().1,
322            Zone {
323                sign: false,
324                hour_offset: 5,
325                minute_offset: 23
326            }
327        );
328
329        assert_eq!(
330            time(b"06:44 +0100").unwrap().1,
331            TimeWithZone {
332                time: Time {
333                    hour: 6,
334                    minute: 44,
335                    second: 0
336                },
337                zone: Zone {
338                    sign: true,
339                    hour_offset: 1,
340                    minute_offset: 0
341                }
342            }
343        );
344        assert_eq!(
345            time(b"23:57 +0000").unwrap().1,
346            TimeWithZone {
347                time: Time {
348                    hour: 23,
349                    minute: 57,
350                    second: 0
351                },
352                zone: Zone {
353                    sign: true,
354                    hour_offset: 0,
355                    minute_offset: 0
356                }
357            }
358        );
359        assert_eq!(
360            time(b"08:23:02 -0500").unwrap().1,
361            TimeWithZone {
362                time: Time {
363                    hour: 8,
364                    minute: 23,
365                    second: 2
366                },
367                zone: Zone {
368                    sign: false,
369                    hour_offset: 5,
370                    minute_offset: 0
371                }
372            }
373        );
374    }
375}