Skip to content

Commit

Permalink
feat(agenda): implement agenda with eventinstances
Browse files Browse the repository at this point in the history
Up until now, the agenda only listed the start date of recurring events.
Now it lists all occurrences of the date range shown by the calendar.
  • Loading branch information
b1rger committed Jan 8, 2024
1 parent 41d9b27 commit 437704c
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 206 deletions.
14 changes: 7 additions & 7 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
// SPDX-License-Identifier: MIT

use crate::cli::Cli;
use crate::config::StyleType;
use crate::config::{Config, Theme};
use crate::config::{Style, StyleType};
use crate::events::Event;
use crate::events::EventInstances;
use anyhow::Result;
use chrono::prelude::*;
use clap::Parser;
Expand All @@ -15,9 +15,9 @@ pub struct Context {
pub usersetdate: chrono::NaiveDate,
pub opts: Cli,
pub config: Config,
pub eventstuple: Vec<(Event, Style)>,
pub theme: Theme,
pub styletype: StyleType,
pub eventinstances: EventInstances,
}

impl Context {
Expand Down Expand Up @@ -45,9 +45,9 @@ impl Context {
usersetdate,
opts,
config,
eventstuple: vec![],
theme,
styletype,
eventinstances: vec![],
})
}
}
Expand All @@ -58,9 +58,9 @@ impl Default for Context {
usersetdate: NaiveDate::default(),
opts: Cli::default(),
config: Config::default(),
eventstuple: vec![],
theme: Theme::default(),
styletype: StyleType::Light,
eventinstances: vec![],
}
}
}
Expand All @@ -72,11 +72,11 @@ mod tests {
#[test]
fn test_context_default() {
let ctx = Context::default();
assert!(ctx.eventstuple.is_empty());
assert!(ctx.eventinstances.is_empty());
}
#[test]
fn test_context_new() {
let ctx = Context::new().unwrap();
assert!(ctx.eventstuple.is_empty());
assert!(ctx.eventinstances.is_empty());
}
}
156 changes: 76 additions & 80 deletions src/events/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
mod ics;
pub use ics::ReadFromIcsFile;

use crate::config::Style;
use crate::utils::convertstyle;
use chrono::prelude::*;
use chrono::Duration;
use rrule::{RRuleSet, Tz};
use std::fmt;

Expand All @@ -27,6 +30,22 @@ impl EventDateTime {
}
}

#[derive(Debug, Clone)]
pub struct EventInstance {
pub date: chrono::NaiveDate,
pub event: Event,
pub style: Style,
}

impl fmt::Display for EventInstance {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let style = convertstyle(self.style.stylenames.to_vec(), "·");
write!(f, "{} {}: {}", style, self.date, self.event.summary)
}
}

pub type EventInstances = Vec<EventInstance>;

#[derive(Debug, Clone)]
pub struct Event {
pub start: EventDateTime,
Expand All @@ -35,6 +54,63 @@ pub struct Event {
pub summary: String,
}

impl Event {
pub fn instances(&self, start: &NaiveDate, end: &NaiveDate, style: &Style) -> EventInstances {
let timezone: Tz = Local::now().timezone().into();
let before = timezone
.with_ymd_and_hms(end.year(), end.month(), end.day(), 23, 59, 59)
.unwrap();
let after = timezone
.with_ymd_and_hms(start.year(), start.month(), start.day(), 0, 0, 0)
.unwrap();
let duration = *end - *start;
let mut eventinstances: EventInstances = vec![];
if self.rrulesets.is_empty() {
let mut date = self.start.date();
if date == self.end.date() {
if start <= &date && &date <= end {
eventinstances.push(EventInstance {
date,
event: self.clone(),
style: style.clone(),
});
}
} else {
while date < self.end.date() {
if start <= &date && &date <= end {
eventinstances.push(EventInstance {
date,
event: self.clone(),
style: style.clone(),
});
}
date += Duration::days(1);
}
}
} else {
for rruleset in &self.rrulesets {
let ruleset = rruleset
.clone()
.before(before)
.after(after)
.all(duration.num_days() as u16);
eventinstances.append(
&mut ruleset
.dates
.iter()
.map(|date| EventInstance {
date: date.date_naive(),
event: self.clone(),
style: style.clone(),
})
.collect::<EventInstances>(),
);
}
}
eventinstances
}
}

pub type Events = Vec<Event>;

impl Default for Event {
Expand All @@ -48,47 +124,6 @@ impl Default for Event {
}
}

impl Event {
pub fn is_day(&self, date: &chrono::NaiveDate) -> bool {
self.in_range(*date, *date)
}

pub fn in_range(
&self,
daterangebegin: chrono::NaiveDate,
daterangeend: chrono::NaiveDate,
) -> bool {
let timezone: Tz = Local::now().timezone().into();
let before = timezone
.with_ymd_and_hms(
daterangeend.year(),
daterangeend.month(),
daterangeend.day(),
23,
59,
59,
)
.unwrap();
let after = timezone
.with_ymd_and_hms(
daterangebegin.year(),
daterangebegin.month(),
daterangebegin.day(),
0,
0,
0,
)
.unwrap();
for rruleset in &self.rrulesets {
let rresult = rruleset.clone().before(before).after(after).all(1);
if !rresult.dates.is_empty() {
return true;
}
}
daterangebegin <= self.start.date() && self.end.date() <= daterangeend
}
}

impl fmt::Display for Event {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let startformatstring = match self.start {
Expand Down Expand Up @@ -120,45 +155,6 @@ mod tests {
let date = NaiveDate::default();
assert_eq!(event.start, EventDateTime::Date(date));
}

#[test]
fn test_event_is_day() {
let event = Event::default();
let date = NaiveDate::default();
assert!(event.is_day(&date));
}
#[test]
fn test_event_is_yearly_day() {
let date = NaiveDate::default();
let event = Event {
end: EventDateTime::Date(date),
..Default::default()
};
assert!(event.is_day(&date));
}
#[test]
fn test_event_is_monthy_day() {
let event = Event {
..Default::default()
};
let date = NaiveDate::default();
assert!(event.is_day(&date));
}
#[test]
fn test_event_is_daily_day() {
let event = Event {
..Default::default()
};
let date = NaiveDate::default();
assert!(event.is_day(&date));
}
/*#[test]
fn test_event_get_end_date_case1() {
let mut event = Event::default();
let date = NaiveDateTime::default();
event.end = Some(EventDateTime::DateTime(date));
assert_eq!(event.get_end_date(), date);
}*/
#[test]
fn test_event_get_end_date_case2() {
let date = NaiveDate::default();
Expand Down
12 changes: 7 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,16 @@ fn main() {
months.push(ctx.usersetdate);
}

// mabye we should use a pointer instead of cloning the style?
for icalstyle in &ctx.config.ical {
let mut icsevents = Events::read_from_ics_file(&icalstyle.file);
icsevents.retain(|event| event.in_range(daterangebegin, daterangeend));
for event in icsevents {
ctx.eventstuple.push((event, icalstyle.style.clone()));
for event in Events::read_from_ics_file(&icalstyle.file) {
ctx.eventinstances.append(&mut event.instances(
&daterangebegin,
&daterangeend,
&icalstyle.style,
));
}
}
ctx.eventinstances.sort_by(|a, b| a.date.cmp(&b.date));

let calendar = Calendar {
dates: months,
Expand Down
53 changes: 2 additions & 51 deletions src/output/agenda.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
//
// SPDX-License-Identifier: MIT

use crate::utils::convertstyle;
use crate::Context;
use chrono::Datelike;

use std::fmt;

Expand All @@ -16,57 +14,10 @@ impl fmt::Display for Agenda<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut ret: String = String::new();
if self.ctx.opts.agenda {
ret = "Agenda\n".to_string();
let eventstuple = &mut self.ctx.eventstuple.clone();
eventstuple.sort_by(|(aevent, _), (bevent, _)| {
aevent
.start
.date()
.month()
.cmp(&bevent.start.date().month())
.then(aevent.start.date().day().cmp(&bevent.start.date().day()))
});
for (event, style) in eventstuple {
ret += format!(
"{} {}\n",
convertstyle(style.stylenames.to_vec(), "·"),
event
)
.as_str();
for pe in self.ctx.eventinstances.iter() {
ret += format!("{pe}\n").as_str();
}
}
write!(f, "{}", ret)
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::config::Style;
use crate::events::{Event, EventDateTime};
use chrono::{Duration, NaiveDate};

#[test]
fn test_fmt() {
let mut ctx = Context::default();
ctx.opts.agenda = true;
let e1: Event = Event {
summary: String::from("Fake Event"),
..Default::default()
};
let e2: Event = Event {
start: EventDateTime::Date(NaiveDate::default() + Duration::weeks(56)),
summary: String::from("Fake Event"),
..Default::default()
};
let s1: Style = Style::default();
let s2: Style = Style::default();
ctx.eventstuple.push((e1, s1));
ctx.eventstuple.push((e2, s2));
let a = Agenda { ctx: &ctx };
assert_eq![
format!("{}", a),
String::from("Agenda\n· Thu, Jan, 1: Fake Event\n· Thu, Jan, 28: Fake Event\n")
];
}
}
Loading

0 comments on commit 437704c

Please sign in to comment.