use chrono::{DateTime, Months, TimeZone, Utc};
use chrono_tz::Tz as ChronoTz;
use color_eyre::eyre::{Context, Result};
use ical::parser::ical::component::IcalCalendar;
use ical::IcalParser;
use indent::indent_all_by;
use log::debug;
use rrule::Tz as RruleTz;
use std::io::BufRead;
use std::rc::Rc;
use std::{collections::HashSet, fmt};
use super::event::{EventList, UnparsedProperties};
use crate::configuration::calendar_source_config::CalendarSourceConfig;
use crate::model::event::Event;
const START_DATETIME_FORMAT: &str = "%a %B %d, %Y";
const END_DATETIME_FORMAT: &str = "%a %B %d, %Y";
#[derive(Debug, PartialEq)]
pub struct Calendar {
name: Option<String>,
title: String,
source_config: Rc<CalendarSourceConfig>,
description: Option<String>,
pub(crate) start: DateTime<Utc>,
pub(crate) end: DateTime<Utc>,
events: EventList,
recurring_events: EventList,
unparsed_properties: UnparsedProperties,
}
impl Eq for Calendar {}
impl fmt::Display for Calendar {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}\n {} to {}\n {} events, {} recurring events\n{}",
self.name.as_ref().unwrap_or(&"NO NAME".to_string()),
self.start.format(START_DATETIME_FORMAT),
self.end().format(END_DATETIME_FORMAT),
self.events.len(),
self.recurring_events.len(),
indent_all_by(
4,
self.description
.as_ref()
.unwrap_or(&"NO DESCRIPTION".to_string())
)
)
}
}
impl Calendar {
pub fn new(
calendar: &IcalCalendar,
source_config: Rc<CalendarSourceConfig>,
) -> Result<Calendar> {
let mut name = None;
let mut description = None;
for property in &calendar.properties {
match property.name.as_str() {
"X-WR-CALNAME" => name = property.value.clone(),
"X-WR-CALDESC" => description = property.value.clone(),
_ => {
}
}
}
let now = Utc::now();
let title = source_config
.title
.clone()
.or(name.clone())
.unwrap_or("No Calendar Name Found".to_owned());
let mut unparsed_properties: UnparsedProperties = HashSet::new();
let mut events: EventList = Vec::new();
let mut recurring_events: EventList = Vec::new();
let mut start = now;
let mut end = now + Months::new(1);
log::debug!("parsing calendar events...");
for event in &calendar.events {
let (new_event, event_unparsed_properties) = Event::new(event, source_config.clone())?;
unparsed_properties.extend(event_unparsed_properties.into_iter());
start = start.min(new_event.start());
end = end.max(new_event.end());
match &new_event.rrule().wrap_err("could not parse rrule")? {
Some(rrules) => {
if let Some(end_date) = rrules
.get_rrule()
.iter()
.filter_map(|r| r.get_until())
.reduce(|accum, date| accum.max(date))
{
end = end.max(end_date.with_timezone(&Utc));
}
recurring_events.push(Rc::new(new_event))
}
None => {
events.push(Rc::new(new_event))
}
}
}
debug!("calendar {:?} runs from {} to {}", name, start, end);
Ok(Calendar {
name,
title,
source_config,
description,
start,
end,
events,
recurring_events,
unparsed_properties,
})
}
pub fn expand_recurrences(
&mut self,
cal_start: DateTime<ChronoTz>,
cal_end: DateTime<ChronoTz>,
tz: &ChronoTz,
) -> Result<()> {
log::debug!("expanding recurrences for calendar: {:?}", self.name);
log::debug!("calendar runs from '{}' to '{}'", cal_start, cal_end);
let repeat_start: DateTime<RruleTz> =
rrule::Tz::UTC.from_utc_datetime(&cal_start.naive_utc());
let repeat_end: DateTime<RruleTz> = rrule::Tz::UTC.from_utc_datetime(&cal_end.naive_utc());
let mut new_events: EventList = Vec::new();
for event in self.recurring_events() {
println!("Event with rrule found: {:#?}", event);
if let Ok(Some(rrule)) = event.rrule() {
for recurrence_time in &rrule.after(repeat_start).before(repeat_end) {
log::debug!(
"adding duplicate event with recurrence_time: {}",
recurrence_time
);
new_events.push(Rc::new(
event.duplicate_with_date(recurrence_time.with_timezone(tz)),
));
}
};
}
log::debug!("adding {} new_events to calendar events", new_events.len());
self.events.extend(new_events);
Ok(())
}
pub fn parse_calendars<B>(
buf: B,
source_config: Rc<CalendarSourceConfig>,
) -> Result<Vec<Calendar>>
where
B: BufRead,
{
log::debug!("parsing calendars...");
let mut calendars = Vec::new();
let reader = IcalParser::new(buf);
for calendar in reader.flatten() {
calendars.push(Calendar::new(&calendar, source_config.clone())?);
}
Ok(calendars)
}
#[must_use]
pub fn start(&self) -> DateTime<Utc> {
self.start
}
#[must_use]
pub fn end(&self) -> DateTime<Utc> {
self.end
}
#[must_use]
pub fn events(&self) -> &[Rc<Event>] {
self.events.as_ref()
}
#[must_use]
pub fn recurring_events(&self) -> &[Rc<Event>] {
self.recurring_events.as_ref()
}
}