improve ical date parsing
This commit is contained in:
parent
7687065bfc
commit
ff823a10f0
5 changed files with 82 additions and 51 deletions
|
@ -287,7 +287,7 @@ impl QRead<Property> for Property {
|
|||
.is_some()
|
||||
{
|
||||
let dtstr = xml.tag_string().await?;
|
||||
let dt = NaiveDateTime::parse_from_str(dtstr.as_str(), CALDAV_DATETIME_FMT)?.and_utc();
|
||||
let dt = NaiveDateTime::parse_from_str(dtstr.as_str(), UTC_DATETIME_FMT)?.and_utc();
|
||||
xml.close().await?;
|
||||
return Ok(Property::MaxDateTime(dt));
|
||||
}
|
||||
|
@ -653,8 +653,8 @@ impl QRead<Expand> for Expand {
|
|||
_ => return Err(ParsingError::MissingAttribute),
|
||||
};
|
||||
|
||||
let start = NaiveDateTime::parse_from_str(rstart.as_str(), CALDAV_DATETIME_FMT)?.and_utc();
|
||||
let end = NaiveDateTime::parse_from_str(rend.as_str(), CALDAV_DATETIME_FMT)?.and_utc();
|
||||
let start = NaiveDateTime::parse_from_str(rstart.as_str(), UTC_DATETIME_FMT)?.and_utc();
|
||||
let end = NaiveDateTime::parse_from_str(rend.as_str(), UTC_DATETIME_FMT)?.and_utc();
|
||||
if start > end {
|
||||
return Err(ParsingError::InvalidValue);
|
||||
}
|
||||
|
@ -672,8 +672,8 @@ impl QRead<LimitRecurrenceSet> for LimitRecurrenceSet {
|
|||
_ => return Err(ParsingError::MissingAttribute),
|
||||
};
|
||||
|
||||
let start = NaiveDateTime::parse_from_str(rstart.as_str(), CALDAV_DATETIME_FMT)?.and_utc();
|
||||
let end = NaiveDateTime::parse_from_str(rend.as_str(), CALDAV_DATETIME_FMT)?.and_utc();
|
||||
let start = NaiveDateTime::parse_from_str(rstart.as_str(), UTC_DATETIME_FMT)?.and_utc();
|
||||
let end = NaiveDateTime::parse_from_str(rend.as_str(), UTC_DATETIME_FMT)?.and_utc();
|
||||
if start > end {
|
||||
return Err(ParsingError::InvalidValue);
|
||||
}
|
||||
|
@ -691,8 +691,8 @@ impl QRead<LimitFreebusySet> for LimitFreebusySet {
|
|||
_ => return Err(ParsingError::MissingAttribute),
|
||||
};
|
||||
|
||||
let start = NaiveDateTime::parse_from_str(rstart.as_str(), CALDAV_DATETIME_FMT)?.and_utc();
|
||||
let end = NaiveDateTime::parse_from_str(rend.as_str(), CALDAV_DATETIME_FMT)?.and_utc();
|
||||
let start = NaiveDateTime::parse_from_str(rstart.as_str(), UTC_DATETIME_FMT)?.and_utc();
|
||||
let end = NaiveDateTime::parse_from_str(rend.as_str(), UTC_DATETIME_FMT)?.and_utc();
|
||||
if start > end {
|
||||
return Err(ParsingError::InvalidValue);
|
||||
}
|
||||
|
@ -918,13 +918,13 @@ impl QRead<TimeRange> for TimeRange {
|
|||
|
||||
let start = match xml.prev_attr("start") {
|
||||
Some(r) => {
|
||||
Some(NaiveDateTime::parse_from_str(r.as_str(), CALDAV_DATETIME_FMT)?.and_utc())
|
||||
Some(NaiveDateTime::parse_from_str(r.as_str(), UTC_DATETIME_FMT)?.and_utc())
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let end = match xml.prev_attr("end") {
|
||||
Some(r) => {
|
||||
Some(NaiveDateTime::parse_from_str(r.as_str(), CALDAV_DATETIME_FMT)?.and_utc())
|
||||
Some(NaiveDateTime::parse_from_str(r.as_str(), UTC_DATETIME_FMT)?.and_utc())
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
|
|
@ -178,7 +178,7 @@ impl QWrite for Property {
|
|||
let start = xml.create_cal_element("min-date-time");
|
||||
let end = start.to_end();
|
||||
|
||||
let dtstr = format!("{}", dt.format(CALDAV_DATETIME_FMT));
|
||||
let dtstr = format!("{}", dt.format(UTC_DATETIME_FMT));
|
||||
xml.q.write_event_async(Event::Start(start.clone())).await?;
|
||||
xml.q
|
||||
.write_event_async(Event::Text(BytesText::new(dtstr.as_str())))
|
||||
|
@ -189,7 +189,7 @@ impl QWrite for Property {
|
|||
let start = xml.create_cal_element("max-date-time");
|
||||
let end = start.to_end();
|
||||
|
||||
let dtstr = format!("{}", dt.format(CALDAV_DATETIME_FMT));
|
||||
let dtstr = format!("{}", dt.format(UTC_DATETIME_FMT));
|
||||
xml.q.write_event_async(Event::Start(start.clone())).await?;
|
||||
xml.q
|
||||
.write_event_async(Event::Text(BytesText::new(dtstr.as_str())))
|
||||
|
@ -493,11 +493,11 @@ impl QWrite for Expand {
|
|||
let mut empty = xml.create_cal_element("expand");
|
||||
empty.push_attribute((
|
||||
"start",
|
||||
format!("{}", self.0.format(CALDAV_DATETIME_FMT)).as_str(),
|
||||
format!("{}", self.0.format(UTC_DATETIME_FMT)).as_str(),
|
||||
));
|
||||
empty.push_attribute((
|
||||
"end",
|
||||
format!("{}", self.1.format(CALDAV_DATETIME_FMT)).as_str(),
|
||||
format!("{}", self.1.format(UTC_DATETIME_FMT)).as_str(),
|
||||
));
|
||||
xml.q.write_event_async(Event::Empty(empty)).await
|
||||
}
|
||||
|
@ -508,11 +508,11 @@ impl QWrite for LimitRecurrenceSet {
|
|||
let mut empty = xml.create_cal_element("limit-recurrence-set");
|
||||
empty.push_attribute((
|
||||
"start",
|
||||
format!("{}", self.0.format(CALDAV_DATETIME_FMT)).as_str(),
|
||||
format!("{}", self.0.format(UTC_DATETIME_FMT)).as_str(),
|
||||
));
|
||||
empty.push_attribute((
|
||||
"end",
|
||||
format!("{}", self.1.format(CALDAV_DATETIME_FMT)).as_str(),
|
||||
format!("{}", self.1.format(UTC_DATETIME_FMT)).as_str(),
|
||||
));
|
||||
xml.q.write_event_async(Event::Empty(empty)).await
|
||||
}
|
||||
|
@ -523,11 +523,11 @@ impl QWrite for LimitFreebusySet {
|
|||
let mut empty = xml.create_cal_element("limit-freebusy-set");
|
||||
empty.push_attribute((
|
||||
"start",
|
||||
format!("{}", self.0.format(CALDAV_DATETIME_FMT)).as_str(),
|
||||
format!("{}", self.0.format(UTC_DATETIME_FMT)).as_str(),
|
||||
));
|
||||
empty.push_attribute((
|
||||
"end",
|
||||
format!("{}", self.1.format(CALDAV_DATETIME_FMT)).as_str(),
|
||||
format!("{}", self.1.format(UTC_DATETIME_FMT)).as_str(),
|
||||
));
|
||||
xml.q.write_event_async(Event::Empty(empty)).await
|
||||
}
|
||||
|
@ -737,20 +737,20 @@ impl QWrite for TimeRange {
|
|||
match self {
|
||||
Self::OnlyStart(start) => empty.push_attribute((
|
||||
"start",
|
||||
format!("{}", start.format(CALDAV_DATETIME_FMT)).as_str(),
|
||||
format!("{}", start.format(UTC_DATETIME_FMT)).as_str(),
|
||||
)),
|
||||
Self::OnlyEnd(end) => empty.push_attribute((
|
||||
"end",
|
||||
format!("{}", end.format(CALDAV_DATETIME_FMT)).as_str(),
|
||||
format!("{}", end.format(UTC_DATETIME_FMT)).as_str(),
|
||||
)),
|
||||
Self::FullRange(start, end) => {
|
||||
empty.push_attribute((
|
||||
"start",
|
||||
format!("{}", start.format(CALDAV_DATETIME_FMT)).as_str(),
|
||||
format!("{}", start.format(UTC_DATETIME_FMT)).as_str(),
|
||||
));
|
||||
empty.push_attribute((
|
||||
"end",
|
||||
format!("{}", end.format(CALDAV_DATETIME_FMT)).as_str(),
|
||||
format!("{}", end.format(UTC_DATETIME_FMT)).as_str(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
use super::types as dav;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
pub const ICAL_DATETIME_FMT: &str = "%Y%m%dT%H%M%S";
|
||||
pub const CALDAV_DATETIME_FMT: &str = "%Y%m%dT%H%M%SZ";
|
||||
pub const FLOATING_DATETIME_FMT: &str = "%Y%m%dT%H%M%S";
|
||||
pub const UTC_DATETIME_FMT: &str = "%Y%m%dT%H%M%SZ";
|
||||
|
||||
//@FIXME ACL (rfc3744) is missing, required
|
||||
//@FIXME Versioning (rfc3253) is missing, required
|
||||
|
|
|
@ -380,6 +380,22 @@ fn apply_filter<'a>(
|
|||
})
|
||||
}
|
||||
|
||||
fn ical_parse_date(dt: &str) -> Option<chrono::DateTime<chrono::Utc>> {
|
||||
tracing::trace!(raw_time = dt, "VEVENT raw time");
|
||||
let tmpl = match dt.chars().last() {
|
||||
Some('Z') => cal::UTC_DATETIME_FMT,
|
||||
Some(_) => {
|
||||
tracing::warn!(raw_time=dt, "floating datetime is not properly supported yet");
|
||||
cal::FLOATING_DATETIME_FMT
|
||||
},
|
||||
None => return None
|
||||
};
|
||||
|
||||
NaiveDateTime::parse_from_str(dt, tmpl)
|
||||
.ok()
|
||||
.map(|v| v.and_utc())
|
||||
}
|
||||
|
||||
fn prop_date(
|
||||
properties: &[icalendar::parser::Property],
|
||||
name: &str,
|
||||
|
@ -388,12 +404,7 @@ fn prop_date(
|
|||
.iter()
|
||||
.find(|candidate| candidate.name.as_str() == name)
|
||||
.map(|p| p.val.as_str())
|
||||
.map(|raw_time| {
|
||||
tracing::trace!(raw_time = raw_time, "VEVENT raw time");
|
||||
NaiveDateTime::parse_from_str(raw_time, cal::ICAL_DATETIME_FMT)
|
||||
.ok()
|
||||
.map(|v| v.and_utc())
|
||||
})
|
||||
.map(ical_parse_date)
|
||||
.flatten()
|
||||
}
|
||||
|
||||
|
@ -412,12 +423,7 @@ fn is_properties_match(props: &[icalendar::parser::Property], filters: &[cal::Pr
|
|||
// check value
|
||||
match &pattern.time_or_text {
|
||||
Some(cal::TimeOrText::Time(time_range)) => {
|
||||
let maybe_parsed_date = NaiveDateTime::parse_from_str(
|
||||
prop.val.as_str(),
|
||||
cal::ICAL_DATETIME_FMT,
|
||||
)
|
||||
.ok()
|
||||
.map(|v| v.and_utc());
|
||||
let maybe_parsed_date = ical_parse_date(prop.val.as_str());
|
||||
|
||||
let parsed_date = match maybe_parsed_date {
|
||||
None => return false,
|
||||
|
|
|
@ -592,9 +592,9 @@ fn rfc4791_webdav_caldav() {
|
|||
assert_eq!(resp.status(), 201);
|
||||
|
||||
// A generic function to check a <calendar-data/> query result
|
||||
let check_full_cal =
|
||||
let check_cal =
|
||||
|multistatus: &dav::Multistatus<All>,
|
||||
(ref_path, ref_etag, ref_ical): (&str, &str, &[u8])| {
|
||||
(ref_path, ref_etag, ref_ical): (&str, Option<&str>, Option<&[u8]>)| {
|
||||
let obj_stats = multistatus
|
||||
.responses
|
||||
.iter()
|
||||
|
@ -616,11 +616,10 @@ fn rfc4791_webdav_caldav() {
|
|||
.0
|
||||
.iter()
|
||||
.find_map(|p| match p {
|
||||
dav::AnyProperty::Value(dav::Property::GetEtag(x)) => Some(x),
|
||||
dav::AnyProperty::Value(dav::Property::GetEtag(x)) => Some(x.as_str()),
|
||||
_ => None,
|
||||
})
|
||||
.expect("etag is return in propstats");
|
||||
assert_eq!(etag.as_str(), ref_etag);
|
||||
});
|
||||
assert_eq!(etag, ref_etag);
|
||||
let calendar_data = obj_success
|
||||
.prop
|
||||
.0
|
||||
|
@ -628,11 +627,10 @@ fn rfc4791_webdav_caldav() {
|
|||
.find_map(|p| match p {
|
||||
dav::AnyProperty::Value(dav::Property::Extension(
|
||||
realization::Property::Cal(cal::Property::CalendarData(x)),
|
||||
)) => Some(x),
|
||||
)) => Some(x.payload.as_bytes()),
|
||||
_ => None,
|
||||
})
|
||||
.expect("calendar data is returned in propstats");
|
||||
assert_eq!(calendar_data.payload.as_bytes(), ref_ical);
|
||||
});
|
||||
assert_eq!(calendar_data, ref_ical);
|
||||
};
|
||||
|
||||
// --- AUTODISCOVERY ---
|
||||
|
@ -720,16 +718,43 @@ fn rfc4791_webdav_caldav() {
|
|||
]
|
||||
.iter()
|
||||
.for_each(|(ref_path, ref_etag, ref_ical)| {
|
||||
check_full_cal(
|
||||
check_cal(
|
||||
&multistatus,
|
||||
(
|
||||
ref_path,
|
||||
ref_etag.to_str().expect("etag header convertible to str"),
|
||||
ref_ical,
|
||||
Some(ref_etag.to_str().expect("etag header convertible to str")),
|
||||
Some(ref_ical),
|
||||
),
|
||||
)
|
||||
});
|
||||
|
||||
// 8.2.1.2. Synchronize by Time Range (here: July 2006)
|
||||
let cal_query = r#" <?xml version="1.0" encoding="utf-8" ?>
|
||||
<C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
|
||||
<D:prop>
|
||||
<D:getetag/>
|
||||
</D:prop>
|
||||
<C:filter>
|
||||
<C:comp-filter name="VCALENDAR">
|
||||
<C:comp-filter name="VEVENT">
|
||||
<C:time-range start="20060701T000000Z" end="20060801T000000Z"/>
|
||||
</C:comp-filter>
|
||||
</C:comp-filter>
|
||||
</C:filter>
|
||||
</C:calendar-query>"#;
|
||||
let resp = http
|
||||
.request(
|
||||
reqwest::Method::from_bytes(b"REPORT")?,
|
||||
"http://localhost:8087/alice/calendar/Personal/",
|
||||
)
|
||||
.body(cal_query)
|
||||
.send()?;
|
||||
assert_eq!(resp.status(), 207);
|
||||
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&resp.text()?);
|
||||
assert_eq!(multistatus.responses.len(), 1);
|
||||
check_cal(&multistatus, ("/alice/calendar/Personal/rfc2.ics", Some(obj2_etag.to_str().expect("etag header convertible to str")), None));
|
||||
|
||||
|
||||
// --- REPORT calendar-multiget ---
|
||||
let cal_query = r#"<?xml version="1.0" encoding="utf-8" ?>
|
||||
<C:calendar-multiget xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
|
||||
|
@ -756,12 +781,12 @@ fn rfc4791_webdav_caldav() {
|
|||
]
|
||||
.iter()
|
||||
.for_each(|(ref_path, ref_etag, ref_ical)| {
|
||||
check_full_cal(
|
||||
check_cal(
|
||||
&multistatus,
|
||||
(
|
||||
ref_path,
|
||||
ref_etag.to_str().expect("etag header convertible to str"),
|
||||
ref_ical,
|
||||
Some(ref_etag.to_str().expect("etag header convertible to str")),
|
||||
Some(ref_ical),
|
||||
),
|
||||
)
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue