1use abnf_core::{
2 is_digit,
3 streaming::{dquote, sp},
4};
5use chrono::{
6 FixedOffset, LocalResult, NaiveDate as ChronoNaiveDate, NaiveDateTime, NaiveTime, TimeZone,
7};
8use imap_types::datetime::{DateTime, NaiveDate};
9use nom::{
10 branch::alt,
11 bytes::streaming::{tag, tag_no_case, take_while_m_n},
12 character::streaming::char,
13 combinator::{map, map_res, value},
14 sequence::{delimited, preceded, tuple},
15};
16
17use crate::decode::{IMAPErrorKind, IMAPParseError, IMAPResult};
18
19pub(crate) fn date(input: &[u8]) -> IMAPResult<&[u8], Option<NaiveDate>> {
23 alt((date_text, delimited(dquote, date_text, dquote)))(input)
24}
25
26pub(crate) fn date_text(input: &[u8]) -> IMAPResult<&[u8], Option<NaiveDate>> {
30 let mut parser = tuple((date_day, tag(b"-"), date_month, tag(b"-"), date_year));
31
32 let (remaining, (d, _, m, _, y)) = parser(input)?;
33
34 Ok((
35 remaining,
36 ChronoNaiveDate::from_ymd_opt(y.into(), m.into(), d.into()).map(NaiveDate::unvalidated),
37 ))
38}
39
40pub(crate) fn date_day(input: &[u8]) -> IMAPResult<&[u8], u8> {
46 digit_1_2(input)
47}
48
49pub(crate) fn date_month(input: &[u8]) -> IMAPResult<&[u8], u8> {
55 alt((
56 value(1, tag_no_case(b"Jan")),
57 value(2, tag_no_case(b"Feb")),
58 value(3, tag_no_case(b"Mar")),
59 value(4, tag_no_case(b"Apr")),
60 value(5, tag_no_case(b"May")),
61 value(6, tag_no_case(b"Jun")),
62 value(7, tag_no_case(b"Jul")),
63 value(8, tag_no_case(b"Aug")),
64 value(9, tag_no_case(b"Sep")),
65 value(10, tag_no_case(b"Oct")),
66 value(11, tag_no_case(b"Nov")),
67 value(12, tag_no_case(b"Dec")),
68 ))(input)
69}
70
71pub(crate) fn date_year(input: &[u8]) -> IMAPResult<&[u8], u16> {
75 digit_4(input)
76}
77
78pub(crate) fn time(input: &[u8]) -> IMAPResult<&[u8], Option<NaiveTime>> {
84 let mut parser = tuple((digit_2, tag(b":"), digit_2, tag(b":"), digit_2));
85
86 let (remaining, (h, _, m, _, s)) = parser(input)?;
87
88 Ok((
89 remaining,
90 NaiveTime::from_hms_opt(h.into(), m.into(), s.into()),
91 ))
92}
93
94pub(crate) fn date_time(input: &[u8]) -> IMAPResult<&[u8], DateTime> {
102 let mut parser = delimited(
103 dquote,
104 tuple((
105 date_day_fixed,
106 tag(b"-"),
107 date_month,
108 tag(b"-"),
109 date_year,
110 sp,
111 time,
112 sp,
113 zone,
114 )),
115 dquote,
116 );
117
118 let (remaining, (d, _, m, _, y, _, time, _, zone)) = parser(input)?;
119
120 let date = ChronoNaiveDate::from_ymd_opt(y.into(), m.into(), d.into());
121
122 match (date, time, zone) {
123 (Some(date), Some(time), Some(zone)) => {
124 let local_datetime = NaiveDateTime::new(date, time);
125
126 if let LocalResult::Single(datetime) = zone.from_local_datetime(&local_datetime) {
127 Ok((remaining, DateTime::unvalidated(datetime)))
128 } else {
129 Err(nom::Err::Failure(IMAPParseError {
130 input,
131 kind: IMAPErrorKind::BadDateTime,
132 }))
133 }
134 }
135 _ => Err(nom::Err::Failure(IMAPParseError {
136 input,
137 kind: IMAPErrorKind::BadDateTime,
138 })),
139 }
140}
141
142pub(crate) fn date_day_fixed(input: &[u8]) -> IMAPResult<&[u8], u8> {
148 alt((
149 map(
150 preceded(sp, take_while_m_n(1, 1, is_digit)),
151 |bytes: &[u8]| bytes[0] - b'0',
152 ),
153 digit_2,
154 ))(input)
155}
156
157pub(crate) fn zone(input: &[u8]) -> IMAPResult<&[u8], Option<FixedOffset>> {
167 let mut parser = tuple((alt((char('+'), char('-'))), digit_2, digit_2));
168
169 let (remaining, (sign, hh, mm)) = parser(input)?;
170
171 let offset = 3600 * (hh as i32) + 60 * (mm as i32);
172
173 let zone = match sign {
174 '+' => FixedOffset::east_opt(offset),
175 '-' => FixedOffset::west_opt(offset),
176 _ => unreachable!(),
177 };
178
179 Ok((remaining, zone))
180}
181
182fn digit_1_2(input: &[u8]) -> IMAPResult<&[u8], u8> {
183 map_res(
184 map(take_while_m_n(1, 2, is_digit), |bytes| {
185 std::str::from_utf8(bytes).unwrap()
189 }),
190 str::parse::<u8>,
191 )(input)
192}
193
194fn digit_2(input: &[u8]) -> IMAPResult<&[u8], u8> {
195 map_res(
196 map(take_while_m_n(2, 2, is_digit), |bytes| {
197 std::str::from_utf8(bytes).unwrap()
201 }),
202 str::parse::<u8>,
203 )(input)
204}
205
206fn digit_4(input: &[u8]) -> IMAPResult<&[u8], u16> {
207 map_res(
208 map(take_while_m_n(4, 4, is_digit), |bytes| {
209 std::str::from_utf8(bytes).unwrap()
213 }),
214 str::parse::<u16>,
215 )(input)
216}
217
218#[cfg(test)]
219mod tests {
220 use std::str::from_utf8;
221
222 use super::*;
223 use crate::testing::known_answer_test_encode;
224
225 #[test]
226 fn test_encode_date_time() {
227 let tests = [
228 (
229 DateTime::try_from(
230 chrono::DateTime::parse_from_rfc2822("Mon, 7 Feb 1994 21:52:25 -0800 (PST)")
231 .unwrap(),
232 )
233 .unwrap(),
234 b"\"07-Feb-1994 21:52:25 -0800\"".as_ref(),
235 ),
236 (
237 DateTime::try_from(
238 chrono::DateTime::parse_from_rfc2822("Mon, 7 Feb 0000 21:52:25 -0800 (PST)")
239 .unwrap(),
240 )
241 .unwrap(),
242 b"\"07-Feb-0000 21:52:25 -0800\"".as_ref(),
243 ),
244 ];
245
246 for test in tests {
247 known_answer_test_encode(test);
248 }
249 }
250
251 #[test]
252 fn test_date() {
253 let (rem, val) = date(b"1-Feb-2020xxx").unwrap();
254 assert_eq!(rem, b"xxx");
255 assert_eq!(
256 val,
257 ChronoNaiveDate::from_ymd_opt(2020, 2, 1).map(NaiveDate::unvalidated)
258 );
259
260 let (rem, val) = date(b"\"1-Feb-2020\"xxx").unwrap();
261 assert_eq!(rem, b"xxx");
262 assert_eq!(
263 val,
264 ChronoNaiveDate::from_ymd_opt(2020, 2, 1).map(NaiveDate::unvalidated)
265 );
266
267 let (rem, val) = date(b"\"01-Feb-2020\"xxx").unwrap();
268 assert_eq!(rem, b"xxx");
269 assert_eq!(
270 val,
271 ChronoNaiveDate::from_ymd_opt(2020, 2, 1).map(NaiveDate::unvalidated)
272 );
273 }
274
275 #[test]
276 fn test_date_text() {
277 let (rem, val) = date_text(b"1-Feb-2020").unwrap();
278 assert_eq!(rem, b"");
279 assert_eq!(
280 val,
281 ChronoNaiveDate::from_ymd_opt(2020, 2, 1).map(NaiveDate::unvalidated)
282 );
283 }
284
285 #[test]
286 fn test_date_day() {
287 let (rem, val) = date_day(b"1xxx").unwrap();
288 assert_eq!(rem, b"xxx");
289 assert_eq!(val, 1);
290
291 let (rem, val) = date_day(b"01xxx").unwrap();
292 assert_eq!(rem, b"xxx");
293 assert_eq!(val, 1);
294
295 let (rem, val) = date_day(b"999xxx").unwrap();
296 assert_eq!(rem, b"9xxx");
297 assert_eq!(val, 99);
298 }
299
300 #[test]
301 fn test_date_month() {
302 let (rem, val) = date_month(b"jAn").unwrap();
303 assert_eq!(rem, b"");
304 assert_eq!(val, 1);
305
306 let (rem, val) = date_month(b"DeCxxx").unwrap();
307 assert_eq!(rem, b"xxx");
308 assert_eq!(val, 12);
309 }
310
311 #[test]
312 fn test_date_year() {
313 let (rem, val) = date_year(b"1985xxx").unwrap();
314 assert_eq!(rem, b"xxx");
315 assert_eq!(val, 1985);
316
317 let (rem, val) = date_year(b"1991xxx").unwrap();
318 assert_eq!(rem, b"xxx");
319 assert_eq!(val, 1991);
320 }
321
322 #[test]
323 fn test_date_day_fixed() {
324 let (rem, val) = date_day_fixed(b"00").unwrap();
325 assert_eq!(rem, b"");
326 assert_eq!(val, 0);
327
328 let (rem, val) = date_day_fixed(b" 0").unwrap();
329 assert_eq!(rem, b"");
330 assert_eq!(val, 0);
331
332 let (rem, val) = date_day_fixed(b"99").unwrap();
333 assert_eq!(rem, b"");
334 assert_eq!(val, 99);
335
336 let (rem, val) = date_day_fixed(b" 9").unwrap();
337 assert_eq!(rem, b"");
338 assert_eq!(val, 9);
339 }
340
341 #[test]
342 fn test_time() {
343 assert!(time(b"1:34:56xxx").is_err());
344 assert!(time(b"12:3:56xxx").is_err());
345 assert!(time(b"12:34:5xxx").is_err());
346
347 let (rem, val) = time(b"12:34:56xxx").unwrap();
348 assert_eq!(rem, b"xxx");
349 assert_eq!(val, NaiveTime::from_hms_opt(12, 34, 56));
350
351 let (rem, val) = time(b"99:99:99 ").unwrap();
352 assert_eq!(rem, b" ");
353 assert_eq!(val, NaiveTime::from_hms_opt(99, 99, 99));
354
355 let (rem, val) = time(b"12:34:56").unwrap();
356 assert_eq!(rem, b"");
357 assert_eq!(val, NaiveTime::from_hms_opt(12, 34, 56));
358
359 let (rem, val) = time(b"99:99:99").unwrap();
360 assert_eq!(rem, b"");
361 assert_eq!(val, NaiveTime::from_hms_opt(99, 99, 99));
362 }
363
364 #[test]
365 fn test_date_time() {
366 let (rem, val) = date_time(b"\" 1-Feb-1985 12:34:56 +0100\"xxx").unwrap();
367 assert_eq!(rem, b"xxx");
368
369 let local_datetime = NaiveDateTime::new(
370 ChronoNaiveDate::from_ymd_opt(1985, 2, 1).unwrap(),
371 NaiveTime::from_hms_opt(12, 34, 56).unwrap(),
372 );
373
374 let datetime = DateTime::try_from(
375 FixedOffset::east_opt(3600)
376 .unwrap()
377 .from_local_datetime(&local_datetime)
378 .unwrap(),
379 )
380 .unwrap();
381
382 println!("{:?} == \n{:?}", val, datetime);
383
384 assert_eq!(val, datetime);
385 }
386
387 #[test]
388 fn test_date_time_invalid() {
389 let tests = [
390 b"\" 1-Feb-0000 12:34:56 +0000\"xxx".as_ref(), b"\" 1-Feb-9999 12:34:56 +0000\"xxx", b"\" 1-Feb-0000 12:34:56 -0000\"xxx", b"\" 1-Feb-9999 12:34:56 -0000\"xxx", b"\" 1-Feb-2020 00:00:00 +0100\"xxx", b"\" 1-Feb-0000 12:34:56 +9999\"xxx",
396 b"\" 1-Feb-9999 12:34:56 +9999\"xxx",
397 b"\" 1-Feb-0000 12:34:56 -9999\"xxx",
398 b"\" 1-Feb-9999 12:34:56 -9999\"xxx",
399 b"\" 1-Feb-2020 99:99:99 +0100\"xxx",
400 b"\"31-Feb-2020 00:00:00 +0100\"xxx",
401 b"\"99-Feb-2020 99:99:99 +0100\"xxx",
402 ];
403
404 for test in &tests[..5] {
405 let (rem, datetime) = date_time(test).unwrap();
406 assert_eq!(rem, b"xxx");
407 println!("{} -> {:?}", from_utf8(test).unwrap(), datetime);
408 }
409
410 for test in &tests[5..] {
411 assert!(date_time(test).is_err());
412 }
413 }
414
415 #[test]
416 fn test_zone() {
417 let (rem, val) = zone(b"+0000xxx").unwrap();
418 eprintln!("{:?}", val);
419 assert_eq!(rem, b"xxx");
420 assert_eq!(val, FixedOffset::east_opt(0));
421
422 let (rem, val) = zone(b"+0000").unwrap();
423 eprintln!("{:?}", val);
424 assert_eq!(rem, b"");
425 assert_eq!(val, FixedOffset::east_opt(0));
426
427 let (rem, val) = zone(b"-0205xxx").unwrap();
428 eprintln!("{:?}", val);
429 assert_eq!(rem, b"xxx");
430 assert_eq!(val, FixedOffset::west_opt(2 * 3600 + 5 * 60));
431
432 let (rem, val) = zone(b"-1159").unwrap();
433 eprintln!("{:?}", val);
434 assert_eq!(rem, b"");
435 assert_eq!(val, FixedOffset::west_opt(11 * 3600 + 59 * 60));
436
437 let (rem, val) = zone(b"-1159").unwrap();
438 eprintln!("{:?}", val);
439 assert_eq!(rem, b"");
440 assert_eq!(val, FixedOffset::west_opt(11 * 3600 + 59 * 60));
441 }
442}