mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-13 22:52:22 +00:00
WIP: Janky recurrence rule evaluation
This commit is contained in:
@@ -1,6 +1,108 @@
|
|||||||
|
use super::{RecurrenceFrequency, RecurrenceLimit, RecurrenceRule};
|
||||||
use crate::CalDateTime;
|
use crate::CalDateTime;
|
||||||
|
use chrono::{Datelike, Duration, IsoWeek, NaiveDate, Weekday, WeekdaySet};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use super::{RecurrenceLimit, RecurrenceRule};
|
/*
|
||||||
|
* https://datatracker.ietf.org/doc/html/rfc5545#section-3.3.10
|
||||||
|
+----------+--------+--------+-------+-------+------+-------+------+
|
||||||
|
| |SECONDLY|MINUTELY|HOURLY |DAILY |WEEKLY|MONTHLY|YEARLY|
|
||||||
|
+----------+--------+--------+-------+-------+------+-------+------+
|
||||||
|
|BYMONTH |Limit |Limit |Limit |Limit |Limit |Limit |Expand|
|
||||||
|
+----------+--------+--------+-------+-------+------+-------+------+
|
||||||
|
|BYWEEKNO |N/A |N/A |N/A |N/A |N/A |N/A |Expand|
|
||||||
|
+----------+--------+--------+-------+-------+------+-------+------+
|
||||||
|
|BYYEARDAY |Limit |Limit |Limit |N/A |N/A |N/A |Expand|
|
||||||
|
+----------+--------+--------+-------+-------+------+-------+------+
|
||||||
|
|BYMONTHDAY|Limit |Limit |Limit |Limit |N/A |Expand |Expand|
|
||||||
|
+----------+--------+--------+-------+-------+------+-------+------+
|
||||||
|
|BYDAY |Limit |Limit |Limit |Limit |Expand|Note 1 |Note 2|
|
||||||
|
+----------+--------+--------+-------+-------+------+-------+------+
|
||||||
|
|BYHOUR |Limit |Limit |Limit |Expand |Expand|Expand |Expand|
|
||||||
|
+----------+--------+--------+-------+-------+------+-------+------+
|
||||||
|
|BYMINUTE |Limit |Limit |Expand |Expand |Expand|Expand |Expand|
|
||||||
|
+----------+--------+--------+-------+-------+------+-------+------+
|
||||||
|
|BYSECOND |Limit |Expand |Expand |Expand |Expand|Expand |Expand|
|
||||||
|
+----------+--------+--------+-------+-------+------+-------+------+
|
||||||
|
|BYSETPOS |Limit |Limit |Limit |Limit |Limit |Limit |Limit |
|
||||||
|
+----------+--------+--------+-------+-------+------+-------+------+
|
||||||
|
|
||||||
|
Note 1: Limit if BYMONTHDAY is present; otherwise, special expand
|
||||||
|
for MONTHLY.
|
||||||
|
|
||||||
|
Note 2: Limit if BYYEARDAY or BYMONTHDAY is present; otherwise,
|
||||||
|
special expand for WEEKLY if BYWEEKNO present; otherwise,
|
||||||
|
special expand for MONTHLY if BYMONTH present; otherwise,
|
||||||
|
special expand for YEARLY.
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub(crate) fn is_leap_year(year: i32) -> bool {
|
||||||
|
year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn last_day_of_month(year: i32, month: u8) -> u8 {
|
||||||
|
match month {
|
||||||
|
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
|
||||||
|
4 | 6 | 9 | 11 => 30,
|
||||||
|
2 => {
|
||||||
|
if is_leap_year(year) {
|
||||||
|
29
|
||||||
|
} else {
|
||||||
|
28
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => panic!("invalid month: {}", month),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn list_days_in_month(year: i32, month: u8) -> &'static [i8] {
|
||||||
|
match last_day_of_month(year, month) {
|
||||||
|
28 => &DAYS28,
|
||||||
|
29 => &DAYS29,
|
||||||
|
30 => &DAYS30,
|
||||||
|
31 => &DAYS31,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn first_weekday(year: i32, wkst: Weekday) -> NaiveDate {
|
||||||
|
// The first week of the year (starting from WKST) is the week having at
|
||||||
|
// least four days in the year
|
||||||
|
// isoweek_start marks week 1 with WKST
|
||||||
|
let mut isoweek_start = NaiveDate::from_isoywd_opt(year, 1, wkst).unwrap();
|
||||||
|
if isoweek_start.year() == year && isoweek_start.day0() >= 4 {
|
||||||
|
// We can fit another week before
|
||||||
|
isoweek_start -= Duration::days(7);
|
||||||
|
}
|
||||||
|
isoweek_start
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn weeks_in_year(year: i32, wkst: Weekday) -> (NaiveDate, u8) {
|
||||||
|
let first_day = first_weekday(year, wkst);
|
||||||
|
let next_year_first_day = first_weekday(year + 1, wkst);
|
||||||
|
(
|
||||||
|
first_day,
|
||||||
|
(next_year_first_day - first_day).num_weeks() as u8,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writing this bodge was easier than doing it the hard way :D
|
||||||
|
const DAYS28: [i8; 28] = [
|
||||||
|
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
|
||||||
|
27, 28,
|
||||||
|
];
|
||||||
|
const DAYS29: [i8; 29] = [
|
||||||
|
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
|
||||||
|
27, 28, 29,
|
||||||
|
];
|
||||||
|
const DAYS30: [i8; 30] = [
|
||||||
|
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
|
||||||
|
27, 28, 29, 30,
|
||||||
|
];
|
||||||
|
const DAYS31: [i8; 31] = [
|
||||||
|
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
|
||||||
|
27, 28, 29, 30, 31,
|
||||||
|
];
|
||||||
|
|
||||||
impl RecurrenceRule {
|
impl RecurrenceRule {
|
||||||
pub fn between(
|
pub fn between(
|
||||||
@@ -9,15 +111,9 @@ impl RecurrenceRule {
|
|||||||
end: Option<CalDateTime>,
|
end: Option<CalDateTime>,
|
||||||
limit: Option<usize>,
|
limit: Option<usize>,
|
||||||
) -> Vec<CalDateTime> {
|
) -> Vec<CalDateTime> {
|
||||||
let start = start;
|
let mut end = end.as_ref();
|
||||||
// Terrible code, should clean this up later.
|
|
||||||
let mut end = end;
|
|
||||||
if let Some(RecurrenceLimit::Until(until)) = &self.limit {
|
if let Some(RecurrenceLimit::Until(until)) = &self.limit {
|
||||||
let mut _end = end.unwrap_or(until.clone());
|
end = Some(end.unwrap_or(until).min(until));
|
||||||
if until.utc() < _end.utc() {
|
|
||||||
_end = until.clone();
|
|
||||||
}
|
|
||||||
end = Some(_end);
|
|
||||||
}
|
}
|
||||||
let mut count = if let Some(RecurrenceLimit::Count(count)) = &self.limit {
|
let mut count = if let Some(RecurrenceLimit::Count(count)) = &self.limit {
|
||||||
*count
|
*count
|
||||||
@@ -28,15 +124,73 @@ impl RecurrenceRule {
|
|||||||
count = count.min(limit)
|
count = count.min(limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut datetimes = vec![start.clone()];
|
let datetimes = vec![];
|
||||||
let mut datetime_utc = start.utc();
|
|
||||||
|
let mut year = start.year();
|
||||||
|
let mut month0 = start.month0();
|
||||||
|
// let months0 = self.bymonth0.clone().unwrap_or(vec![start.month0() as i8]);
|
||||||
|
|
||||||
|
let offset_weekdays = self.offset_weekdays();
|
||||||
|
let absolute_weekdays = self.absolute_weekdays();
|
||||||
|
|
||||||
while datetimes.len() < count {
|
while datetimes.len() < count {
|
||||||
if let Some(end) = &end {
|
let mut result_dates = vec![start.date()];
|
||||||
if datetime_utc > end.utc() {
|
// Iterate over frequency*interval
|
||||||
break;
|
match self.frequency {
|
||||||
|
RecurrenceFrequency::Yearly => year += self.interval as i32,
|
||||||
|
RecurrenceFrequency::Monthly => {
|
||||||
|
month0 += self.interval;
|
||||||
|
year += (month0 as f32 / 12.).floor() as i32;
|
||||||
|
month0 %= 12;
|
||||||
}
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
datetimes.push(datetime_utc.into());
|
|
||||||
|
#[allow(clippy::single_match)]
|
||||||
|
match self.frequency {
|
||||||
|
RecurrenceFrequency::Yearly => {}
|
||||||
|
// RecurrenceFrequency::Monthly => {
|
||||||
|
// // Filter bymonth
|
||||||
|
// if let Some(bymonth0) = &self.bymonth0 {
|
||||||
|
// if !bymonth0.contains(&(month0 as u8)) {
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if let Some(monthdays) = &self.bymonthday {
|
||||||
|
// for monthday in monthdays {
|
||||||
|
// let monthday = if *monthday > 0 {
|
||||||
|
// *monthday as u32
|
||||||
|
// } else {
|
||||||
|
// // +1 because -1 is the last day
|
||||||
|
// last_day_of_month(year, month0 as u8 + 1) as u32
|
||||||
|
// + 1
|
||||||
|
// // monthday is negative
|
||||||
|
// + *monthday as u32
|
||||||
|
// };
|
||||||
|
// let date = if let Some(date) =
|
||||||
|
// NaiveDate::from_ymd_opt(year, month0 as u32 + 1, monthday)
|
||||||
|
// {
|
||||||
|
// date
|
||||||
|
// } else {
|
||||||
|
// continue;
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// if let Some(weekdays) = absolute_weekdays {
|
||||||
|
// if weekdays.contains(date.weekday()) {
|
||||||
|
// dates.insert(date);
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// dates.insert(date);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(end) = end {}
|
||||||
|
// datetimes.push(datetime.to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
datetimes
|
datetimes
|
||||||
@@ -49,6 +203,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_between() {
|
fn test_between() {
|
||||||
|
// Example: Last workday of the month
|
||||||
let rrule = RecurrenceRule::parse("FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1").unwrap();
|
let rrule = RecurrenceRule::parse("FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1").unwrap();
|
||||||
let start = CalDateTime::parse("20250516T133000Z", None).unwrap();
|
let start = CalDateTime::parse("20250516T133000Z", None).unwrap();
|
||||||
assert_eq!(rrule.between(start, None, Some(4)), vec![]);
|
assert_eq!(rrule.between(start, None, Some(4)), vec![]);
|
||||||
|
|||||||
0
crates/ical/src/rrule/iter_monthly.rs
Normal file
0
crates/ical/src/rrule/iter_monthly.rs
Normal file
175
crates/ical/src/rrule/iter_yearly.rs
Normal file
175
crates/ical/src/rrule/iter_yearly.rs
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use chrono::{Datelike, Duration, NaiveDate};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
RecurrenceRule,
|
||||||
|
iter::{last_day_of_month, list_days_in_month, weeks_in_year},
|
||||||
|
};
|
||||||
|
|
||||||
|
impl RecurrenceRule {
|
||||||
|
pub fn week_expansion(&self, year: i32) -> Option<HashSet<NaiveDate>> {
|
||||||
|
let absolute_weekdays = self.absolute_weekdays();
|
||||||
|
|
||||||
|
if let Some(byweekno) = &self.byweekno {
|
||||||
|
let mut dates = HashSet::new();
|
||||||
|
|
||||||
|
let weekstart = self.week_start.unwrap_or(chrono::Weekday::Mon);
|
||||||
|
let (first_weekstart, num_weeks) = weeks_in_year(year, weekstart);
|
||||||
|
|
||||||
|
let weeknums0: Vec<u8> = byweekno
|
||||||
|
.iter()
|
||||||
|
.map(|num| {
|
||||||
|
if *num < 0 {
|
||||||
|
(num_weeks as i8 + *num) as u8
|
||||||
|
} else {
|
||||||
|
(*num - 1) as u8
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for weeknum0 in weeknums0 {
|
||||||
|
let weekstart_date = first_weekstart + Duration::weeks(weeknum0 as i64);
|
||||||
|
// Iterate over the week and check if the weekdays are allowed
|
||||||
|
for i in 0..7 {
|
||||||
|
let date = weekstart_date + Duration::days(i);
|
||||||
|
if let Some(weekdays) = absolute_weekdays {
|
||||||
|
if weekdays.contains(date.weekday()) {
|
||||||
|
dates.insert(date);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dates.insert(date);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(dates)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn month_expansion(&self, year: i32) -> Option<HashSet<NaiveDate>> {
|
||||||
|
let offset_weekdays = self.offset_weekdays();
|
||||||
|
let absolute_weekdays = self.absolute_weekdays();
|
||||||
|
|
||||||
|
if let Some(bymonth0) = &self.bymonth0 {
|
||||||
|
let mut dates = HashSet::new();
|
||||||
|
for month0 in bymonth0 {
|
||||||
|
// Add BYMONTHDAY or all days
|
||||||
|
let monthdays = self
|
||||||
|
.bymonthday
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or(list_days_in_month(year, month0 + 1));
|
||||||
|
|
||||||
|
for monthday in monthdays {
|
||||||
|
let monthday = if *monthday > 0 {
|
||||||
|
*monthday as u32
|
||||||
|
} else {
|
||||||
|
// +1 because -1 is the last day
|
||||||
|
last_day_of_month(year, month0 + 1) as u32
|
||||||
|
+ 1
|
||||||
|
// monthday is negative
|
||||||
|
+ *monthday as u32
|
||||||
|
};
|
||||||
|
let date = if let Some(date) =
|
||||||
|
NaiveDate::from_ymd_opt(year, *month0 as u32 + 1, monthday)
|
||||||
|
{
|
||||||
|
date
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(weekdays) = absolute_weekdays {
|
||||||
|
if weekdays.contains(date.weekday()) {
|
||||||
|
dates.insert(date);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dates.insert(date);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add offset weekdays
|
||||||
|
if let Some(offset_weekdays) = &offset_weekdays {
|
||||||
|
for (num, day) in offset_weekdays.iter() {
|
||||||
|
let date = if *num > 0 {
|
||||||
|
NaiveDate::from_weekday_of_month_opt(
|
||||||
|
year,
|
||||||
|
*month0 as u32 + 1,
|
||||||
|
*day,
|
||||||
|
*num as u8,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// If index negative:
|
||||||
|
// Go to first day of next month and walk back the weeks
|
||||||
|
NaiveDate::from_weekday_of_month_opt(
|
||||||
|
year,
|
||||||
|
*month0 as u32 + 1 + 1,
|
||||||
|
*day,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.map(|date| date + Duration::weeks(*num))
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(date) = date {
|
||||||
|
dates.insert(date);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(dates)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dates_yearly(
|
||||||
|
&self,
|
||||||
|
start: NaiveDate,
|
||||||
|
end: Option<NaiveDate>,
|
||||||
|
limit: usize,
|
||||||
|
) -> Vec<NaiveDate> {
|
||||||
|
let mut dates = vec![start];
|
||||||
|
let mut year = start.year();
|
||||||
|
|
||||||
|
while dates.len() < limit {
|
||||||
|
// Expand BYMONTH
|
||||||
|
let month_expansion = self.month_expansion(year);
|
||||||
|
|
||||||
|
// Expand BYWEEKNO
|
||||||
|
let week_expansion = self.week_expansion(year);
|
||||||
|
|
||||||
|
let mut occurence_set = match (month_expansion, week_expansion) {
|
||||||
|
(Some(month_expansion), Some(week_expansion)) => month_expansion
|
||||||
|
.intersection(&week_expansion)
|
||||||
|
.cloned()
|
||||||
|
.collect(),
|
||||||
|
(Some(month_expansion), None) => month_expansion,
|
||||||
|
(None, Some(week_expansion)) => week_expansion,
|
||||||
|
(None, None) => start
|
||||||
|
.with_year(year)
|
||||||
|
.map(|date| HashSet::from([date]))
|
||||||
|
.unwrap_or_default(),
|
||||||
|
}
|
||||||
|
.into_iter()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
occurence_set.sort();
|
||||||
|
if let Some(bysetpos) = &self.bysetpos {
|
||||||
|
occurence_set = bysetpos
|
||||||
|
.iter()
|
||||||
|
.filter_map(|i| {
|
||||||
|
if *i > 0 {
|
||||||
|
occurence_set.get((*i - 1) as usize)
|
||||||
|
} else {
|
||||||
|
occurence_set.get((occurence_set.len() as i64 + *i) as usize)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
dates.extend_from_slice(occurence_set.as_slice());
|
||||||
|
year += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
dates
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
use super::{CalDateTime, CalDateTimeError};
|
use super::{CalDateTime, CalDateTimeError};
|
||||||
use chrono::Weekday;
|
use chrono::{Weekday, WeekdaySet};
|
||||||
use std::{num::ParseIntError, str::FromStr};
|
use std::{num::ParseIntError, str::FromStr};
|
||||||
use strum_macros::EnumString;
|
use strum_macros::EnumString;
|
||||||
|
|
||||||
mod iter;
|
mod iter;
|
||||||
|
mod iter_monthly;
|
||||||
|
mod iter_yearly;
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum ParserError {
|
pub enum ParserError {
|
||||||
@@ -70,7 +72,7 @@ pub struct RecurrenceRule {
|
|||||||
pub frequency: RecurrenceFrequency,
|
pub frequency: RecurrenceFrequency,
|
||||||
pub limit: Option<RecurrenceLimit>,
|
pub limit: Option<RecurrenceLimit>,
|
||||||
// Repeat every n-th time
|
// Repeat every n-th time
|
||||||
pub interval: usize,
|
pub interval: u32,
|
||||||
|
|
||||||
pub bysecond: Option<Vec<usize>>,
|
pub bysecond: Option<Vec<usize>>,
|
||||||
pub byminute: Option<Vec<usize>>,
|
pub byminute: Option<Vec<usize>>,
|
||||||
@@ -79,7 +81,7 @@ pub struct RecurrenceRule {
|
|||||||
pub bymonthday: Option<Vec<i8>>,
|
pub bymonthday: Option<Vec<i8>>,
|
||||||
pub byyearday: Option<Vec<i64>>,
|
pub byyearday: Option<Vec<i64>>,
|
||||||
pub byweekno: Option<Vec<i8>>,
|
pub byweekno: Option<Vec<i8>>,
|
||||||
pub bymonth: Option<Vec<i8>>,
|
pub bymonth0: Option<Vec<u8>>,
|
||||||
pub week_start: Option<Weekday>,
|
pub week_start: Option<Weekday>,
|
||||||
// Selects the n-th occurence within an a recurrence rule
|
// Selects the n-th occurence within an a recurrence rule
|
||||||
pub bysetpos: Option<Vec<i64>>,
|
pub bysetpos: Option<Vec<i64>>,
|
||||||
@@ -97,7 +99,7 @@ impl RecurrenceRule {
|
|||||||
let mut bymonthday = None;
|
let mut bymonthday = None;
|
||||||
let mut byyearday = None;
|
let mut byyearday = None;
|
||||||
let mut byweekno = None;
|
let mut byweekno = None;
|
||||||
let mut bymonth = None;
|
let mut bymonth0 = None;
|
||||||
let mut week_start = None;
|
let mut week_start = None;
|
||||||
let mut bysetpos = None;
|
let mut bysetpos = None;
|
||||||
|
|
||||||
@@ -175,10 +177,13 @@ impl RecurrenceRule {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
("BYMONTH", val) => {
|
("BYMONTH", val) => {
|
||||||
bymonth = Some(
|
bymonth0 = Some(
|
||||||
val.split(',')
|
val.split(',')
|
||||||
.map(|val| val.parse())
|
.map(|val| val.parse())
|
||||||
.collect::<Result<Vec<_>, _>>()?,
|
.collect::<Result<Vec<u8>, _>>()?
|
||||||
|
.into_iter()
|
||||||
|
.map(|month| month - 1)
|
||||||
|
.collect(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
("WKST", val) => week_start = Some(IcalWeekday::from_str(val)?.into()),
|
("WKST", val) => week_start = Some(IcalWeekday::from_str(val)?.into()),
|
||||||
@@ -203,13 +208,44 @@ impl RecurrenceRule {
|
|||||||
bymonthday,
|
bymonthday,
|
||||||
byyearday,
|
byyearday,
|
||||||
byweekno,
|
byweekno,
|
||||||
bymonth,
|
bymonth0,
|
||||||
week_start,
|
week_start,
|
||||||
bysetpos,
|
bysetpos,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl RecurrenceRule {
|
||||||
|
fn offset_weekdays(&self) -> Option<Vec<(i64, Weekday)>> {
|
||||||
|
if let Some(byday) = self.byday.as_ref() {
|
||||||
|
Some(
|
||||||
|
byday
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(offset, day)| offset.map(|offset| (offset, day.clone())))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn absolute_weekdays(&self) -> Option<WeekdaySet> {
|
||||||
|
if let Some(byday) = self.byday.as_ref() {
|
||||||
|
Some(WeekdaySet::from_iter(byday.iter().filter_map(
|
||||||
|
|(offset, day)| {
|
||||||
|
if &None == offset {
|
||||||
|
Some(day.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{ParserError, RecurrenceRule};
|
use super::{ParserError, RecurrenceRule};
|
||||||
|
|||||||
@@ -228,6 +228,13 @@ impl CalDateTime {
|
|||||||
CalDateTime::Date(date) => date.and_time(NaiveTime::default()).and_utc(),
|
CalDateTime::Date(date) => date.and_time(NaiveTime::default()).and_utc(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn timezone(&self) -> Option<CalTimezone> {
|
||||||
|
match &self {
|
||||||
|
CalDateTime::DateTime(datetime) => Some(datetime.timezone()),
|
||||||
|
CalDateTime::Date(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<CalDateTime> for DateTime<Utc> {
|
impl From<CalDateTime> for DateTime<Utc> {
|
||||||
@@ -340,6 +347,34 @@ impl Datelike for CalDateTime {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CalDateTime {
|
||||||
|
pub fn inc_year(&self, interval: u32) -> Option<Self> {
|
||||||
|
self.with_year(self.year() + interval as i32)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increments the year until a valid date is found
|
||||||
|
pub fn inc_find_year(&self, interval: u32) -> Self {
|
||||||
|
let mut year = self.year();
|
||||||
|
loop {
|
||||||
|
year += interval as i32;
|
||||||
|
if let Some(date) = self.with_year(year) {
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inc_month(&self, interval: u32) -> Self {
|
||||||
|
let mut month0 = self.month0();
|
||||||
|
loop {
|
||||||
|
month0 += interval;
|
||||||
|
if month0 >= 12 {}
|
||||||
|
if let Some(date) = self.with_month0(month0) {
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::CalDateTime;
|
use crate::CalDateTime;
|
||||||
|
|||||||
Reference in New Issue
Block a user