use abnf_core::{
is_digit,
streaming::{dquote, sp},
};
use chrono::{
FixedOffset, LocalResult, NaiveDate as ChronoNaiveDate, NaiveDateTime, NaiveTime, TimeZone,
};
pub use imap_types::datetime::*;
use nom::{
branch::alt,
bytes::streaming::{tag, tag_no_case, take_while_m_n},
character::streaming::char,
combinator::{map, map_res, value},
sequence::{delimited, preceded, tuple},
};
use crate::codec::{IMAPErrorKind, IMAPParseError, IMAPResult};
pub(crate) fn date(input: &[u8]) -> IMAPResult<&[u8], Option<NaiveDate>> {
alt((date_text, delimited(dquote, date_text, dquote)))(input)
}
pub(crate) fn date_text(input: &[u8]) -> IMAPResult<&[u8], Option<NaiveDate>> {
let mut parser = tuple((date_day, tag(b"-"), date_month, tag(b"-"), date_year));
let (remaining, (d, _, m, _, y)) = parser(input)?;
Ok((
remaining,
ChronoNaiveDate::from_ymd_opt(y.into(), m.into(), d.into()).map(NaiveDate::unvalidated),
))
}
pub(crate) fn date_day(input: &[u8]) -> IMAPResult<&[u8], u8> {
digit_1_2(input)
}
pub(crate) fn date_month(input: &[u8]) -> IMAPResult<&[u8], u8> {
alt((
value(1, tag_no_case(b"Jan")),
value(2, tag_no_case(b"Feb")),
value(3, tag_no_case(b"Mar")),
value(4, tag_no_case(b"Apr")),
value(5, tag_no_case(b"May")),
value(6, tag_no_case(b"Jun")),
value(7, tag_no_case(b"Jul")),
value(8, tag_no_case(b"Aug")),
value(9, tag_no_case(b"Sep")),
value(10, tag_no_case(b"Oct")),
value(11, tag_no_case(b"Nov")),
value(12, tag_no_case(b"Dec")),
))(input)
}
pub(crate) fn date_year(input: &[u8]) -> IMAPResult<&[u8], u16> {
digit_4(input)
}
pub(crate) fn time(input: &[u8]) -> IMAPResult<&[u8], Option<NaiveTime>> {
let mut parser = tuple((digit_2, tag(b":"), digit_2, tag(b":"), digit_2));
let (remaining, (h, _, m, _, s)) = parser(input)?;
Ok((
remaining,
NaiveTime::from_hms_opt(h.into(), m.into(), s.into()),
))
}
pub(crate) fn date_time(input: &[u8]) -> IMAPResult<&[u8], DateTime> {
let mut parser = delimited(
dquote,
tuple((
date_day_fixed,
tag(b"-"),
date_month,
tag(b"-"),
date_year,
sp,
time,
sp,
zone,
)),
dquote,
);
let (remaining, (d, _, m, _, y, _, time, _, zone)) = parser(input)?;
let date = ChronoNaiveDate::from_ymd_opt(y.into(), m.into(), d.into());
match (date, time, zone) {
(Some(date), Some(time), Some(zone)) => {
let local_datetime = NaiveDateTime::new(date, time);
if let LocalResult::Single(datetime) = zone.from_local_datetime(&local_datetime) {
Ok((remaining, DateTime::unvalidated(datetime)))
} else {
Err(nom::Err::Failure(IMAPParseError {
input,
kind: IMAPErrorKind::BadDateTime,
}))
}
}
_ => Err(nom::Err::Failure(IMAPParseError {
input,
kind: IMAPErrorKind::BadDateTime,
})),
}
}
pub(crate) fn date_day_fixed(input: &[u8]) -> IMAPResult<&[u8], u8> {
alt((
map(
preceded(sp, take_while_m_n(1, 1, is_digit)),
|bytes: &[u8]| bytes[0] - b'0',
),
digit_2,
))(input)
}
pub(crate) fn zone(input: &[u8]) -> IMAPResult<&[u8], Option<FixedOffset>> {
let mut parser = tuple((alt((char('+'), char('-'))), digit_2, digit_2));
let (remaining, (sign, hh, mm)) = parser(input)?;
let offset = 3600 * (hh as i32) + 60 * (mm as i32);
let zone = match sign {
'+' => FixedOffset::east_opt(offset),
'-' => FixedOffset::west_opt(offset),
_ => unreachable!(),
};
Ok((remaining, zone))
}
fn digit_1_2(input: &[u8]) -> IMAPResult<&[u8], u8> {
map_res(
map(take_while_m_n(1, 2, is_digit), |bytes| {
std::str::from_utf8(bytes).unwrap()
}),
str::parse::<u8>,
)(input)
}
fn digit_2(input: &[u8]) -> IMAPResult<&[u8], u8> {
map_res(
map(take_while_m_n(2, 2, is_digit), |bytes| {
std::str::from_utf8(bytes).unwrap()
}),
str::parse::<u8>,
)(input)
}
fn digit_4(input: &[u8]) -> IMAPResult<&[u8], u16> {
map_res(
map(take_while_m_n(4, 4, is_digit), |bytes| {
std::str::from_utf8(bytes).unwrap()
}),
str::parse::<u16>,
)(input)
}
#[cfg(test)]
mod tests {
use std::str::from_utf8;
use super::*;
use crate::testing::known_answer_test_encode;
#[test]
fn test_encode_date_time() {
let tests = [
(
DateTime::try_from(
chrono::DateTime::parse_from_rfc2822("Mon, 7 Feb 1994 21:52:25 -0800 (PST)")
.unwrap(),
)
.unwrap(),
b"\"07-Feb-1994 21:52:25 -0800\"".as_ref(),
),
(
DateTime::try_from(
chrono::DateTime::parse_from_rfc2822("Mon, 7 Feb 0000 21:52:25 -0800 (PST)")
.unwrap(),
)
.unwrap(),
b"\"07-Feb-0000 21:52:25 -0800\"".as_ref(),
),
];
for test in tests {
known_answer_test_encode(test);
}
}
#[test]
fn test_date() {
let (rem, val) = date(b"1-Feb-2020xxx").unwrap();
assert_eq!(rem, b"xxx");
assert_eq!(
val,
ChronoNaiveDate::from_ymd_opt(2020, 2, 1).map(NaiveDate::unvalidated)
);
let (rem, val) = date(b"\"1-Feb-2020\"xxx").unwrap();
assert_eq!(rem, b"xxx");
assert_eq!(
val,
ChronoNaiveDate::from_ymd_opt(2020, 2, 1).map(NaiveDate::unvalidated)
);
let (rem, val) = date(b"\"01-Feb-2020\"xxx").unwrap();
assert_eq!(rem, b"xxx");
assert_eq!(
val,
ChronoNaiveDate::from_ymd_opt(2020, 2, 1).map(NaiveDate::unvalidated)
);
}
#[test]
fn test_date_text() {
let (rem, val) = date_text(b"1-Feb-2020").unwrap();
assert_eq!(rem, b"");
assert_eq!(
val,
ChronoNaiveDate::from_ymd_opt(2020, 2, 1).map(NaiveDate::unvalidated)
);
}
#[test]
fn test_date_day() {
let (rem, val) = date_day(b"1xxx").unwrap();
assert_eq!(rem, b"xxx");
assert_eq!(val, 1);
let (rem, val) = date_day(b"01xxx").unwrap();
assert_eq!(rem, b"xxx");
assert_eq!(val, 1);
let (rem, val) = date_day(b"999xxx").unwrap();
assert_eq!(rem, b"9xxx");
assert_eq!(val, 99);
}
#[test]
fn test_date_month() {
let (rem, val) = date_month(b"jAn").unwrap();
assert_eq!(rem, b"");
assert_eq!(val, 1);
let (rem, val) = date_month(b"DeCxxx").unwrap();
assert_eq!(rem, b"xxx");
assert_eq!(val, 12);
}
#[test]
fn test_date_year() {
let (rem, val) = date_year(b"1985xxx").unwrap();
assert_eq!(rem, b"xxx");
assert_eq!(val, 1985);
let (rem, val) = date_year(b"1991xxx").unwrap();
assert_eq!(rem, b"xxx");
assert_eq!(val, 1991);
}
#[test]
fn test_date_day_fixed() {
let (rem, val) = date_day_fixed(b"00").unwrap();
assert_eq!(rem, b"");
assert_eq!(val, 0);
let (rem, val) = date_day_fixed(b" 0").unwrap();
assert_eq!(rem, b"");
assert_eq!(val, 0);
let (rem, val) = date_day_fixed(b"99").unwrap();
assert_eq!(rem, b"");
assert_eq!(val, 99);
let (rem, val) = date_day_fixed(b" 9").unwrap();
assert_eq!(rem, b"");
assert_eq!(val, 9);
}
#[test]
fn test_time() {
assert!(time(b"1:34:56xxx").is_err());
assert!(time(b"12:3:56xxx").is_err());
assert!(time(b"12:34:5xxx").is_err());
let (rem, val) = time(b"12:34:56xxx").unwrap();
assert_eq!(rem, b"xxx");
assert_eq!(val, NaiveTime::from_hms_opt(12, 34, 56));
let (rem, val) = time(b"99:99:99 ").unwrap();
assert_eq!(rem, b" ");
assert_eq!(val, NaiveTime::from_hms_opt(99, 99, 99));
let (rem, val) = time(b"12:34:56").unwrap();
assert_eq!(rem, b"");
assert_eq!(val, NaiveTime::from_hms_opt(12, 34, 56));
let (rem, val) = time(b"99:99:99").unwrap();
assert_eq!(rem, b"");
assert_eq!(val, NaiveTime::from_hms_opt(99, 99, 99));
}
#[test]
fn test_date_time() {
let (rem, val) = date_time(b"\" 1-Feb-1985 12:34:56 +0100\"xxx").unwrap();
assert_eq!(rem, b"xxx");
let local_datetime = NaiveDateTime::new(
ChronoNaiveDate::from_ymd_opt(1985, 2, 1).unwrap(),
NaiveTime::from_hms_opt(12, 34, 56).unwrap(),
);
let datetime = DateTime::try_from(
FixedOffset::east_opt(3600)
.unwrap()
.from_local_datetime(&local_datetime)
.unwrap(),
)
.unwrap();
println!("{:?} == \n{:?}", val, datetime);
assert_eq!(val, datetime);
}
#[test]
fn test_date_time_invalid() {
let tests = [
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",
b"\" 1-Feb-9999 12:34:56 +9999\"xxx",
b"\" 1-Feb-0000 12:34:56 -9999\"xxx",
b"\" 1-Feb-9999 12:34:56 -9999\"xxx",
b"\" 1-Feb-2020 99:99:99 +0100\"xxx",
b"\"31-Feb-2020 00:00:00 +0100\"xxx",
b"\"99-Feb-2020 99:99:99 +0100\"xxx",
];
for test in &tests[..5] {
let (rem, datetime) = date_time(test).unwrap();
assert_eq!(rem, b"xxx");
println!("{} -> {:?}", from_utf8(test).unwrap(), datetime);
}
for test in &tests[5..] {
assert!(date_time(test).is_err());
}
}
#[test]
fn test_zone() {
let (rem, val) = zone(b"+0000xxx").unwrap();
eprintln!("{:?}", val);
assert_eq!(rem, b"xxx");
assert_eq!(val, FixedOffset::east_opt(0));
let (rem, val) = zone(b"+0000").unwrap();
eprintln!("{:?}", val);
assert_eq!(rem, b"");
assert_eq!(val, FixedOffset::east_opt(0));
let (rem, val) = zone(b"-0205xxx").unwrap();
eprintln!("{:?}", val);
assert_eq!(rem, b"xxx");
assert_eq!(val, FixedOffset::west_opt(2 * 3600 + 5 * 60));
let (rem, val) = zone(b"-1159").unwrap();
eprintln!("{:?}", val);
assert_eq!(rem, b"");
assert_eq!(val, FixedOffset::west_opt(11 * 3600 + 59 * 60));
let (rem, val) = zone(b"-1159").unwrap();
eprintln!("{:?}", val);
assert_eq!(rem, b"");
assert_eq!(val, FixedOffset::west_opt(11 * 3600 + 59 * 60));
}
}