use chrono::{Duration, NaiveDateTime, NaiveDate, Datelike};
use crate::calendar::events::Event;
#[derive(Debug, Clone)]
pub struct RecurrenceRule {
pub frequency: Frequency,
pub interval: u32,
pub until: Option<NaiveDateTime>,
pub count: Option<u32>,
}
#[derive(Debug, Clone)]
pub enum Frequency {
Daily,
Weekly,
Monthly,
Yearly,
}
impl RecurrenceRule {
pub fn from_ics_string(rrule: &str) -> Result<Self, String> {
let mut frequency = None;
let mut interval = 1;
let mut until = None;
let mut count = None;
for part in rrule.split(';') {
let kv: Vec<&str> = part.split('=').collect();
if kv.len() != 2 {
continue;
}
match kv[0] {
"FREQ" => {
frequency = Some(match kv[1] {
"DAILY" => Frequency::Daily,
"WEEKLY" => Frequency::Weekly,
"MONTHLY" => Frequency::Monthly,
"YEARLY" => Frequency::Yearly,
_ => return Err(format!("Unknown frequency: {}", kv[1])),
});
}
"INTERVAL" => {
interval = kv[1].parse()
.map_err(|_| format!("Invalid interval: {}", kv[1]))?;
}
"UNTIL" => {
until = Some(parse_ics_datetime(kv[1])?);
}
"COUNT" => {
count = Some(kv[1].parse()
.map_err(|_| format!("Invalid count: {}", kv[1]))?);
}
_ => {} }
}
let frequency = frequency
.ok_or("FREQ is required in RRULE")?;
Ok(RecurrenceRule {
frequency,
interval,
until,
count,
})
}
pub fn expand_event(&self, base_event: &Event) -> Vec<Event> {
let mut events = vec![base_event.clone()];
let duration = base_event.duration();
let max_occurrences = self.count.unwrap_or(365); let max_date = chrono::Local::now().naive_local().date() + Duration::days(730);
for i in 1..max_occurrences {
let next_start = match self.frequency {
Frequency::Daily => base_event.start + Duration::days(i as i64 * self.interval as i64),
Frequency::Weekly => base_event.start + Duration::weeks(i as i64 * self.interval as i64),
Frequency::Monthly => add_months(base_event.start, i * self.interval),
Frequency::Yearly => add_years(base_event.start, i * self.interval),
};
if let Some(until) = self.until {
if next_start > until {
break;
}
}
if next_start.date() > max_date {
break;
}
let mut next_event = base_event.clone();
next_event.id = format!("{}-{}", base_event.id, i);
next_event.start = next_start;
next_event.end = next_start + duration;
events.push(next_event);
}
events
}
}
fn parse_ics_datetime(datetime_str: &str) -> Result<NaiveDateTime, String> {
if datetime_str.ends_with('Z') && datetime_str.len() == 16 {
let date_part = &datetime_str[0..8];
let time_part = &datetime_str[9..15];
let year: i32 = date_part[0..4].parse()
.map_err(|_| "Invalid year")?;
let month: u32 = date_part[4..6].parse()
.map_err(|_| "Invalid month")?;
let day: u32 = date_part[6..8].parse()
.map_err(|_| "Invalid day")?;
let hour: u32 = time_part[0..2].parse()
.map_err(|_| "Invalid hour")?;
let minute: u32 = time_part[2..4].parse()
.map_err(|_| "Invalid minute")?;
let second: u32 = time_part[4..6].parse()
.map_err(|_| "Invalid second")?;
let date = NaiveDate::from_ymd_opt(year, month, day)
.ok_or("Invalid date")?;
date.and_hms_opt(hour, minute, second)
.ok_or("Invalid time")
.map_err(|e| e.to_string())
}
else if datetime_str.len() == 8 {
let year: i32 = datetime_str[0..4].parse()
.map_err(|_| "Invalid year")?;
let month: u32 = datetime_str[4..6].parse()
.map_err(|_| "Invalid month")?;
let day: u32 = datetime_str[6..8].parse()
.map_err(|_| "Invalid day")?;
let date = NaiveDate::from_ymd_opt(year, month, day)
.ok_or("Invalid date")?;
Ok(date.and_hms_opt(0, 0, 0).unwrap())
}
else {
Err(format!("Unsupported datetime format: {}", datetime_str))
}
}
fn add_months(datetime: NaiveDateTime, months: u32) -> NaiveDateTime {
let mut year = datetime.date().year();
let mut month = datetime.date().month();
let day = datetime.date().day();
month += months;
while month > 12 {
month -= 12;
year += 1;
}
let target_date = loop {
if let Some(date) = NaiveDate::from_ymd_opt(year, month, day) {
break date;
}
if day > 1 {
if let Some(date) = NaiveDate::from_ymd_opt(year, month, day - 1) {
break date;
}
}
let last_day = match month {
2 => if year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) { 29 } else { 28 },
4 | 6 | 9 | 11 => 30,
_ => 31,
};
break NaiveDate::from_ymd_opt(year, month, last_day).unwrap();
};
target_date.and_time(datetime.time())
}
fn add_years(datetime: NaiveDateTime, years: u32) -> NaiveDateTime {
let new_year = datetime.date().year() + years as i32;
let month = datetime.date().month();
let day = datetime.date().day();
let target_date = if month == 2 && day == 29 {
if new_year % 4 == 0 && (new_year % 100 != 0 || new_year % 400 == 0) {
NaiveDate::from_ymd_opt(new_year, month, day).unwrap()
} else {
NaiveDate::from_ymd_opt(new_year, month, 28).unwrap()
}
} else {
NaiveDate::from_ymd_opt(new_year, month, day).unwrap()
};
target_date.and_time(datetime.time())
}