aboutsummaryrefslogtreecommitdiff
path: root/aero-ical/src/parser.rs
diff options
context:
space:
mode:
authorQuentin Dufour <quentin@deuxfleurs.fr>2024-05-25 19:30:59 +0200
committerQuentin Dufour <quentin@deuxfleurs.fr>2024-05-25 19:30:59 +0200
commit52f870633c2cab8a4aeeec74792774931139b8b5 (patch)
tree878d4ff16cdebd7fdfc50a278dbadedd7eb63480 /aero-ical/src/parser.rs
parentff823a10f049e06c711537560ba10f3dc826afcd (diff)
downloadaerogramme-52f870633c2cab8a4aeeec74792774931139b8b5.tar.gz
aerogramme-52f870633c2cab8a4aeeec74792774931139b8b5.zip
add a new aero-ical module
Diffstat (limited to 'aero-ical/src/parser.rs')
-rw-r--r--aero-ical/src/parser.rs138
1 files changed, 138 insertions, 0 deletions
diff --git a/aero-ical/src/parser.rs b/aero-ical/src/parser.rs
new file mode 100644
index 0000000..4354737
--- /dev/null
+++ b/aero-ical/src/parser.rs
@@ -0,0 +1,138 @@
+use chrono::TimeDelta;
+
+use nom::IResult;
+use nom::branch::alt;
+use nom::bytes::complete::{tag, tag_no_case};
+use nom::combinator::{value, opt, map, map_opt};
+use nom::sequence::{pair, tuple};
+use nom::character::complete as nomchar;
+
+use aero_dav::caltypes as cal;
+
+//@FIXME too simple, we have 4 cases in practices:
+// - floating datetime
+// - floating datetime with a tzid as param so convertible to tz datetime
+// - utc datetime
+// - floating(?) date (without time)
+pub fn date_time(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
+ };
+
+ chrono::NaiveDateTime::parse_from_str(dt, tmpl)
+ .ok()
+ .map(|v| v.and_utc())
+}
+
+/// RFC3389 Duration Value
+///
+/// ```abnf
+/// dur-value = (["+"] / "-") "P" (dur-date / dur-time / dur-week)
+/// dur-date = dur-day [dur-time]
+/// dur-time = "T" (dur-hour / dur-minute / dur-second)
+/// dur-week = 1*DIGIT "W"
+/// dur-hour = 1*DIGIT "H" [dur-minute]
+/// dur-minute = 1*DIGIT "M" [dur-second]
+/// dur-second = 1*DIGIT "S"
+/// dur-day = 1*DIGIT "D"
+/// ```
+pub fn dur_value(text: &str) -> IResult<&str, TimeDelta> {
+ map_opt(tuple((
+ dur_sign,
+ tag_no_case("P"),
+ alt((
+ dur_date,
+ dur_time,
+ dur_week,
+ ))
+ )), |(sign, _, delta)| {
+ delta.checked_mul(sign)
+ })(text)
+}
+
+fn dur_sign(text: &str) -> IResult<&str, i32> {
+ map(opt(alt((value(1, tag("+")), value(-1, tag("-"))))), |x| x.unwrap_or(1))(text)
+}
+fn dur_date(text: &str) -> IResult<&str, TimeDelta> {
+ map(pair(dur_day, opt(dur_time)), |(day, time)| day + time.unwrap_or(TimeDelta::zero()))(text)
+}
+fn dur_time(text: &str) -> IResult<&str, TimeDelta> {
+ map(pair(tag_no_case("T"), alt((dur_hour, dur_minute, dur_second))), |(_, x)| x)(text)
+}
+fn dur_week(text: &str) -> IResult<&str, TimeDelta> {
+ map_opt(pair(nomchar::i64, tag_no_case("W")), |(i, _)| TimeDelta::try_weeks(i))(text)
+}
+fn dur_day(text: &str) -> IResult<&str, TimeDelta> {
+ map_opt(pair(nomchar::i64, tag_no_case("D")), |(i, _)| TimeDelta::try_days(i))(text)
+}
+fn dur_hour(text: &str) -> IResult<&str, TimeDelta> {
+ map_opt(tuple((nomchar::i64, tag_no_case("H"), opt(dur_minute))), |(i, _, mm)| {
+ TimeDelta::try_hours(i).map(|hours| hours + mm.unwrap_or(TimeDelta::zero()))
+ })(text)
+}
+fn dur_minute(text: &str) -> IResult<&str, TimeDelta> {
+ map_opt(tuple((nomchar::i64, tag_no_case("M"), opt(dur_second))), |(i, _, ms)| {
+ TimeDelta::try_minutes(i).map(|min| min + ms.unwrap_or(TimeDelta::zero()))
+ })(text)
+}
+fn dur_second(text: &str) -> IResult<&str, TimeDelta> {
+ map_opt(pair(nomchar::i64, tag_no_case("S")), |(i, _)| TimeDelta::try_seconds(i))(text)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn rfc5545_example1() {
+ // A duration of 15 days, 5 hours, and 20 seconds would be:
+ let to_parse = "P15DT5H0M20S";
+ let (_, time_delta) = dur_value(to_parse).unwrap();
+ assert_eq!(
+ time_delta,
+ TimeDelta::try_days(15).unwrap() + TimeDelta::try_hours(5).unwrap() + TimeDelta::try_seconds(20).unwrap());
+ }
+
+ #[test]
+ fn rfc5545_example2() {
+ // A duration of 7 weeks would be:
+ let to_parse = "P7W";
+ let (_, time_delta) = dur_value(to_parse).unwrap();
+ assert_eq!(
+ time_delta,
+ TimeDelta::try_weeks(7).unwrap()
+ );
+ }
+
+ #[test]
+ fn rfc4791_example1() {
+ // 10 minutes before
+ let to_parse = "-PT10M";
+
+ let (_, time_delta) = dur_value(to_parse).unwrap();
+ assert_eq!(
+ time_delta,
+ TimeDelta::try_minutes(-10).unwrap()
+ );
+ }
+
+
+ #[test]
+ fn ical_org_example1() {
+ // The following example is for a "VALARM" calendar component that specifies an email alarm
+ // that will trigger 2 days before the scheduled due DATE-TIME of a to-do with which it is associated.
+ let to_parse = "-P2D";
+
+ let (_, time_delta) = dur_value(to_parse).unwrap();
+ assert_eq!(
+ time_delta,
+ TimeDelta::try_days(-2).unwrap()
+ );
+ }
+}