outsource some more ical logic to ical-rs fork

This commit is contained in:
Lennart
2025-07-26 13:32:28 +02:00
parent 03294ec106
commit 7fc64d219c
6 changed files with 33 additions and 98 deletions

5
Cargo.lock generated
View File

@@ -1598,9 +1598,12 @@ dependencies = [
[[package]] [[package]]
name = "ical" name = "ical"
version = "0.11.0" version = "0.11.0"
source = "git+https://github.com/lennart-k/ical-rs#c5fa2217af23ba27ba80295a2c0eb922f08f6c97" source = "git+https://github.com/lennart-k/ical-rs#c1a88dd68d9de79d6f71d6729aa4cb6e587b0d62"
dependencies = [ dependencies = [
"chrono",
"chrono-tz", "chrono-tz",
"lazy_static",
"regex",
"serde", "serde",
"thiserror 2.0.12", "thiserror 2.0.12",
] ]

View File

@@ -1,53 +0,0 @@
use crate::CalDateTimeError;
use chrono::Duration;
use lazy_static::lazy_static;
lazy_static! {
static ref RE_DURATION: regex::Regex = regex::Regex::new(r"^(?<sign>[+-])?P((?P<W>\d+)W)?((?P<D>\d+)D)?(T((?P<H>\d+)H)?((?P<M>\d+)M)?((?P<S>\d+)S)?)?$").unwrap();
}
pub fn parse_duration(string: &str) -> Result<Duration, CalDateTimeError> {
let captures = RE_DURATION
.captures(string)
.ok_or(CalDateTimeError::InvalidDurationFormat(string.to_string()))?;
let mut duration = Duration::zero();
if let Some(weeks) = captures.name("W") {
duration += Duration::weeks(weeks.as_str().parse().unwrap());
}
if let Some(days) = captures.name("D") {
duration += Duration::days(days.as_str().parse().unwrap());
}
if let Some(hours) = captures.name("H") {
duration += Duration::hours(hours.as_str().parse().unwrap());
}
if let Some(minutes) = captures.name("M") {
duration += Duration::minutes(minutes.as_str().parse().unwrap());
}
if let Some(seconds) = captures.name("S") {
duration += Duration::seconds(seconds.as_str().parse().unwrap());
}
if let Some(sign) = captures.name("sign") {
if sign.as_str() == "-" {
duration = -duration;
}
}
Ok(duration)
}
#[cfg(test)]
mod tests {
use chrono::Duration;
use crate::parse_duration;
#[test]
fn test_parse_duration() {
assert_eq!(parse_duration("P12W").unwrap(), Duration::weeks(12));
assert_eq!(parse_duration("P12D").unwrap(), Duration::days(12));
assert_eq!(parse_duration("PT12H").unwrap(), Duration::hours(12));
assert_eq!(parse_duration("PT12M").unwrap(), Duration::minutes(12));
assert_eq!(parse_duration("PT12S").unwrap(), Duration::seconds(12));
}
}

View File

@@ -1,5 +1,5 @@
use crate::CalDateTime;
use crate::Error; use crate::Error;
use crate::{CalDateTime, parse_duration};
use chrono::{DateTime, Duration, Utc}; use chrono::{DateTime, Duration, Utc};
use ical::parser::ComponentMut; use ical::parser::ComponentMut;
use ical::{generator::IcalEvent, parser::Component, property::Property}; use ical::{generator::IcalEvent, parser::Component, property::Property};
@@ -16,7 +16,7 @@ pub struct EventObject {
impl EventObject { impl EventObject {
pub fn get_dtstart(&self) -> Result<Option<CalDateTime>, Error> { pub fn get_dtstart(&self) -> Result<Option<CalDateTime>, Error> {
if let Some(dtstart) = self.event.get_property("DTSTART") { if let Some(dtstart) = self.event.get_dtstart() {
Ok(Some(CalDateTime::parse_prop(dtstart, &self.timezones)?)) Ok(Some(CalDateTime::parse_prop(dtstart, &self.timezones)?))
} else { } else {
Ok(None) Ok(None)
@@ -24,7 +24,7 @@ impl EventObject {
} }
pub fn get_dtend(&self) -> Result<Option<CalDateTime>, Error> { pub fn get_dtend(&self) -> Result<Option<CalDateTime>, Error> {
if let Some(dtend) = self.event.get_property("DTEND") { if let Some(dtend) = self.event.get_dtend() {
Ok(Some(CalDateTime::parse_prop(dtend, &self.timezones)?)) Ok(Some(CalDateTime::parse_prop(dtend, &self.timezones)?))
} else { } else {
Ok(None) Ok(None)
@@ -32,33 +32,21 @@ impl EventObject {
} }
pub fn get_last_occurence(&self) -> Result<Option<CalDateTime>, Error> { pub fn get_last_occurence(&self) -> Result<Option<CalDateTime>, Error> {
if let Some(_rrule) = self.event.get_property("RRULE") { if self.event.get_rrule().is_some() {
// TODO: understand recurrence rules // TODO: understand recurrence rules
return Ok(None); return Ok(None);
} }
if let Some(dtend) = self.event.get_property("DTEND") { if let Some(dtend) = self.get_dtend()? {
return Ok(Some(CalDateTime::parse_prop(dtend, &self.timezones)?)); return Ok(Some(dtend));
}; };
let duration = self.get_duration()?.unwrap_or(Duration::days(1)); let duration = self.event.get_duration().unwrap_or(Duration::days(1));
let first_occurence = self.get_dtstart()?; let first_occurence = self.get_dtstart()?;
Ok(first_occurence.map(|first_occurence| first_occurence + duration)) Ok(first_occurence.map(|first_occurence| first_occurence + duration))
} }
pub fn get_duration(&self) -> Result<Option<Duration>, Error> {
if let Some(Property {
value: Some(duration),
..
}) = self.event.get_property("DURATION")
{
Ok(Some(parse_duration(duration)?))
} else {
Ok(None)
}
}
pub fn recurrence_ruleset(&self) -> Result<Option<rrule::RRuleSet>, Error> { pub fn recurrence_ruleset(&self) -> Result<Option<rrule::RRuleSet>, Error> {
let dtstart: DateTime<rrule::Tz> = if let Some(dtstart) = self.get_dtstart()? { let dtstart: DateTime<rrule::Tz> = if let Some(dtstart) = self.get_dtstart()? {
if let Some(dtend) = self.get_dtend()? { if let Some(dtend) = self.get_dtend()? {

View File

@@ -3,9 +3,6 @@ mod timezone;
pub use timestamp::*; pub use timestamp::*;
pub use timezone::*; pub use timezone::*;
mod duration;
pub use duration::parse_duration;
mod icalendar; mod icalendar;
pub use icalendar::*; pub use icalendar::*;

View File

@@ -1,4 +1,4 @@
use super::timezone::CalTimezone; use super::timezone::ICalTimezone;
use chrono::{DateTime, Datelike, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, Utc}; use chrono::{DateTime, Datelike, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, Utc};
use chrono_tz::Tz; use chrono_tz::Tz;
use derive_more::derive::Deref; use derive_more::derive::Deref;
@@ -64,8 +64,8 @@ pub enum CalDateTime {
// Form 2, example: 19980119T070000Z -> UTC // Form 2, example: 19980119T070000Z -> UTC
// Form 3, example: TZID=America/New_York:19980119T020000 -> Olson // Form 3, example: TZID=America/New_York:19980119T020000 -> Olson
// https://en.wikipedia.org/wiki/Tz_database // https://en.wikipedia.org/wiki/Tz_database
DateTime(DateTime<CalTimezone>), DateTime(DateTime<ICalTimezone>),
Date(NaiveDate, CalTimezone), Date(NaiveDate, ICalTimezone),
} }
impl From<CalDateTime> for DateTime<rrule::Tz> { impl From<CalDateTime> for DateTime<rrule::Tz> {
@@ -102,13 +102,13 @@ impl Ord for CalDateTime {
impl From<DateTime<Local>> for CalDateTime { impl From<DateTime<Local>> for CalDateTime {
fn from(value: DateTime<Local>) -> Self { fn from(value: DateTime<Local>) -> Self {
CalDateTime::DateTime(value.with_timezone(&CalTimezone::Local)) CalDateTime::DateTime(value.with_timezone(&ICalTimezone::Local))
} }
} }
impl From<DateTime<Utc>> for CalDateTime { impl From<DateTime<Utc>> for CalDateTime {
fn from(value: DateTime<Utc>) -> Self { fn from(value: DateTime<Utc>) -> Self {
CalDateTime::DateTime(value.with_timezone(&CalTimezone::Olson(chrono_tz::UTC))) CalDateTime::DateTime(value.with_timezone(&ICalTimezone::Olson(chrono_tz::UTC)))
} }
} }
@@ -160,7 +160,7 @@ impl CalDateTime {
pub fn format(&self) -> String { pub fn format(&self) -> String {
match self { match self {
Self::DateTime(datetime) => match datetime.timezone() { Self::DateTime(datetime) => match datetime.timezone() {
CalTimezone::Olson(chrono_tz::UTC) => datetime.format(UTC_DATE_TIME).to_string(), ICalTimezone::Olson(chrono_tz::UTC) => datetime.format(UTC_DATE_TIME).to_string(),
_ => datetime.format(LOCAL_DATE_TIME).to_string(), _ => datetime.format(LOCAL_DATE_TIME).to_string(),
}, },
Self::Date(date, _) => date.format(LOCAL_DATE).to_string(), Self::Date(date, _) => date.format(LOCAL_DATE).to_string(),
@@ -185,7 +185,7 @@ impl CalDateTime {
matches!(&self, Self::Date(_, _)) matches!(&self, Self::Date(_, _))
} }
pub fn as_datetime(&self) -> Cow<'_, DateTime<CalTimezone>> { pub fn as_datetime(&self) -> Cow<'_, DateTime<ICalTimezone>> {
match self { match self {
Self::DateTime(datetime) => Cow::Borrowed(datetime), Self::DateTime(datetime) => Cow::Borrowed(datetime),
Self::Date(date, tz) => Cow::Owned( Self::Date(date, tz) => Cow::Owned(
@@ -209,7 +209,7 @@ impl CalDateTime {
} }
return Ok(CalDateTime::DateTime( return Ok(CalDateTime::DateTime(
datetime datetime
.and_local_timezone(CalTimezone::Local) .and_local_timezone(ICalTimezone::Local)
.earliest() .earliest()
.ok_or(CalDateTimeError::LocalTimeGap)?, .ok_or(CalDateTimeError::LocalTimeGap)?,
)); ));
@@ -219,8 +219,8 @@ impl CalDateTime {
return Ok(datetime.and_utc().into()); return Ok(datetime.and_utc().into());
} }
let timezone = timezone let timezone = timezone
.map(CalTimezone::Olson) .map(ICalTimezone::Olson)
.unwrap_or(CalTimezone::Local); .unwrap_or(ICalTimezone::Local);
if let Ok(date) = NaiveDate::parse_from_str(value, LOCAL_DATE) { if let Ok(date) = NaiveDate::parse_from_str(value, LOCAL_DATE) {
return Ok(CalDateTime::Date(date, timezone)); return Ok(CalDateTime::Date(date, timezone));
} }
@@ -252,7 +252,7 @@ impl CalDateTime {
CalDateTime::Date( CalDateTime::Date(
NaiveDate::from_ymd_opt(year, month, day) NaiveDate::from_ymd_opt(year, month, day)
.ok_or(CalDateTimeError::ParseError(value.to_string()))?, .ok_or(CalDateTimeError::ParseError(value.to_string()))?,
CalTimezone::Local, ICalTimezone::Local,
), ),
false, false,
)); ));
@@ -264,7 +264,7 @@ impl CalDateTime {
self.as_datetime().to_utc() self.as_datetime().to_utc()
} }
pub fn timezone(&self) -> CalTimezone { pub fn timezone(&self) -> ICalTimezone {
match &self { match &self {
CalDateTime::DateTime(datetime) => datetime.timezone(), CalDateTime::DateTime(datetime) => datetime.timezone(),
CalDateTime::Date(_, tz) => tz.to_owned(), CalDateTime::Date(_, tz) => tz.to_owned(),
@@ -400,7 +400,7 @@ mod tests {
( (
CalDateTime::Date( CalDateTime::Date(
NaiveDate::from_ymd_opt(1985, 4, 12).unwrap(), NaiveDate::from_ymd_opt(1985, 4, 12).unwrap(),
crate::CalTimezone::Local crate::ICalTimezone::Local
), ),
true true
) )
@@ -410,7 +410,7 @@ mod tests {
( (
CalDateTime::Date( CalDateTime::Date(
NaiveDate::from_ymd_opt(1985, 4, 12).unwrap(), NaiveDate::from_ymd_opt(1985, 4, 12).unwrap(),
crate::CalTimezone::Local crate::ICalTimezone::Local
), ),
true true
) )
@@ -420,7 +420,7 @@ mod tests {
( (
CalDateTime::Date( CalDateTime::Date(
NaiveDate::from_ymd_opt(1972, 4, 12).unwrap(), NaiveDate::from_ymd_opt(1972, 4, 12).unwrap(),
crate::CalTimezone::Local crate::ICalTimezone::Local
), ),
false false
) )

View File

@@ -3,21 +3,21 @@ use chrono_tz::Tz;
use derive_more::{Display, From}; use derive_more::{Display, From};
#[derive(Debug, Clone, From, PartialEq, Eq)] #[derive(Debug, Clone, From, PartialEq, Eq)]
pub enum CalTimezone { pub enum ICalTimezone {
Local, Local,
Olson(Tz), Olson(Tz),
} }
impl From<CalTimezone> for rrule::Tz { impl From<ICalTimezone> for rrule::Tz {
fn from(value: CalTimezone) -> Self { fn from(value: ICalTimezone) -> Self {
match value { match value {
CalTimezone::Local => Self::LOCAL, ICalTimezone::Local => Self::LOCAL,
CalTimezone::Olson(tz) => Self::Tz(tz), ICalTimezone::Olson(tz) => Self::Tz(tz),
} }
} }
} }
impl From<rrule::Tz> for CalTimezone { impl From<rrule::Tz> for ICalTimezone {
fn from(value: rrule::Tz) -> Self { fn from(value: rrule::Tz) -> Self {
match value { match value {
rrule::Tz::Local(_) => Self::Local, rrule::Tz::Local(_) => Self::Local,
@@ -41,7 +41,7 @@ impl chrono::Offset for CalTimezoneOffset {
} }
} }
impl TimeZone for CalTimezone { impl TimeZone for ICalTimezone {
type Offset = CalTimezoneOffset; type Offset = CalTimezoneOffset;
fn from_offset(offset: &Self::Offset) -> Self { fn from_offset(offset: &Self::Offset) -> Self {