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 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}