From 87b7ce200144502bc66aa4d7cee8c2b5623d7440 Mon Sep 17 00:00:00 2001 From: Tom Quist Date: Sun, 24 Dec 2023 11:07:22 +0100 Subject: [PATCH] feat: add getter for rrules, exrules and exdates --- __test__/daily.spec.ts | 11 +- index.d.ts | 3 + src/lib.rs | 323 ++++++++++++++++++++++++++++++++++++++--- 3 files changed, 311 insertions(+), 26 deletions(-) diff --git a/__test__/daily.spec.ts b/__test__/daily.spec.ts index 90c9707..a1ec4a5 100644 --- a/__test__/daily.spec.ts +++ b/__test__/daily.spec.ts @@ -93,7 +93,9 @@ test('Every Monday in January, for 3 years', () => { .setByMonth([Month.January]) .setByWeekday([Weekday.Monday]) .setUntil(949327200000); - const set = new RRuleSet(873205200000, 'US/Eastern').addRrule(rrule); + const set = new RRuleSet(873205200000, 'US/Eastern') + .addRrule(rrule) + .addExdate(949327200000); const asString = set.toString(); const dates = set.all(); @@ -104,6 +106,11 @@ test('Every Monday in January, for 3 years', () => { expect(dates).toEqual([ 884008800000, 884613600000, 885218400000, 885823200000, 915458400000, 916063200000, 916668000000, 917272800000, 946908000000, 947512800000, - 948117600000, 948722400000, 949327200000, + 948117600000, 948722400000, + ]); + expect(set.getRrules().map((r) => r.toString())).toEqual([ + 'FREQ=daily;UNTIL=20000131T140000Z;BYMONTH=1;BYHOUR=9;BYMINUTE=0;BYSECOND=0;BYDAY=MO', ]); + expect(set.getExrules().map((r) => r.toString())).toEqual([]); + expect(set.getExdates()).toEqual([949327200000]); }); diff --git a/index.d.ts b/index.d.ts index c545894..425c26b 100644 --- a/index.d.ts +++ b/index.d.ts @@ -77,6 +77,9 @@ export class RRuleSet { addExdate(timestamp: number): this get dtstart(): number get tzid(): string + getRrules(): RRule[] + getExrules(): RRule[] + getExdates(): number[] all(limit?: number | undefined | null): number[] between(after: number, before: number, inclusive?: boolean | undefined | null): number[] } diff --git a/src/lib.rs b/src/lib.rs index 3da2148..5d1d0c8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ use chrono::{DateTime, Month, TimeZone, Weekday}; use napi::bindgen_prelude::*; use replace_with::replace_with_or_abort; -use rrule::{Frequency, NWeekday, RRule, RRuleSet, Tz, Unvalidated}; +use rrule::{Frequency, NWeekday, RRule, RRuleSet, Tz, Unvalidated, Validated}; #[macro_use] extern crate napi_derive; @@ -46,16 +46,280 @@ pub enum JsMonth { December, } +pub enum RRuleType { + Unvalidated(RRule), + Validated(RRule), +} + +fn to_unvalidated(rrule: &RRule) -> RRule { + let by_month = rrule + .get_by_month() + .iter() + .map(|m| Month::try_from(*m).unwrap()) + .collect::>(); + let mut unvalidated = RRule::new(rrule.get_freq()) + .interval(rrule.get_interval()) + .week_start(rrule.get_week_start()) + .by_set_pos(rrule.get_by_set_pos().to_vec()) + .by_month(&by_month) + .by_month_day(rrule.get_by_month_day().to_vec()) + .by_year_day(rrule.get_by_year_day().to_vec()) + .by_week_no(rrule.get_by_week_no().to_vec()) + .by_weekday(rrule.get_by_weekday().to_vec()) + .by_hour(rrule.get_by_hour().to_vec()) + .by_minute(rrule.get_by_minute().to_vec()) + .by_second(rrule.get_by_second().to_vec()); + + if let Some(count) = rrule.get_count() { + unvalidated = unvalidated.count(count); + } + + if let Some(until) = rrule.get_until() { + unvalidated = unvalidated.until(*until); + } + + unvalidated +} + +impl RRuleType { + pub fn get_freq(&self) -> Frequency { + match self { + RRuleType::Unvalidated(rrule) => rrule.get_freq(), + RRuleType::Validated(rrule) => rrule.get_freq(), + } + } + + pub fn get_interval(&self) -> u16 { + match self { + RRuleType::Unvalidated(rrule) => rrule.get_interval(), + RRuleType::Validated(rrule) => rrule.get_interval(), + } + } + + pub fn get_count(&self) -> Option { + match self { + RRuleType::Unvalidated(rrule) => rrule.get_count(), + RRuleType::Validated(rrule) => rrule.get_count(), + } + } + + pub fn get_by_weekday(&self) -> &[NWeekday] { + match self { + RRuleType::Unvalidated(rrule) => rrule.get_by_weekday(), + RRuleType::Validated(rrule) => rrule.get_by_weekday(), + } + } + + pub fn get_by_hour(&self) -> &[u8] { + match self { + RRuleType::Unvalidated(rrule) => rrule.get_by_hour(), + RRuleType::Validated(rrule) => rrule.get_by_hour(), + } + } + + pub fn get_by_minute(&self) -> &[u8] { + match self { + RRuleType::Unvalidated(rrule) => rrule.get_by_minute(), + RRuleType::Validated(rrule) => rrule.get_by_minute(), + } + } + + pub fn get_by_second(&self) -> &[u8] { + match self { + RRuleType::Unvalidated(rrule) => rrule.get_by_second(), + RRuleType::Validated(rrule) => rrule.get_by_second(), + } + } + + pub fn get_by_month_day(&self) -> &[i8] { + match self { + RRuleType::Unvalidated(rrule) => rrule.get_by_month_day(), + RRuleType::Validated(rrule) => rrule.get_by_month_day(), + } + } + + pub fn get_by_set_pos(&self) -> &[i32] { + match self { + RRuleType::Unvalidated(rrule) => rrule.get_by_set_pos(), + RRuleType::Validated(rrule) => rrule.get_by_set_pos(), + } + } + + pub fn get_by_month(&self) -> &[u8] { + match self { + RRuleType::Unvalidated(rrule) => rrule.get_by_month(), + RRuleType::Validated(rrule) => rrule.get_by_month(), + } + } + + pub fn get_by_week_no(&self) -> &[i8] { + match self { + RRuleType::Unvalidated(rrule) => rrule.get_by_week_no(), + RRuleType::Validated(rrule) => rrule.get_by_week_no(), + } + } + + pub fn get_by_year_day(&self) -> &[i16] { + match self { + RRuleType::Unvalidated(rrule) => rrule.get_by_year_day(), + RRuleType::Validated(rrule) => rrule.get_by_year_day(), + } + } + + pub fn get_week_start(&self) -> Weekday { + match self { + RRuleType::Unvalidated(rrule) => rrule.get_week_start(), + RRuleType::Validated(rrule) => rrule.get_week_start(), + } + } + + pub fn get_until(&self) -> Option<&DateTime> { + match self { + RRuleType::Unvalidated(rrule) => rrule.get_until(), + RRuleType::Validated(rrule) => rrule.get_until(), + } + } + + pub fn interval(self, interval: u16) -> Self { + match self { + RRuleType::Unvalidated(rrule) => RRuleType::Unvalidated(rrule.interval(interval)), + RRuleType::Validated(rrule) => { + RRuleType::Unvalidated(to_unvalidated(&rrule).interval(interval)) + } + } + } + + pub fn count(self, count: u32) -> Self { + match self { + RRuleType::Unvalidated(rrule) => RRuleType::Unvalidated(rrule.count(count)), + RRuleType::Validated(rrule) => RRuleType::Unvalidated(to_unvalidated(&rrule).count(count)), + } + } + + pub fn by_weekday(self, weekdays: Vec) -> Self { + match self { + RRuleType::Unvalidated(rrule) => RRuleType::Unvalidated(rrule.by_weekday(weekdays)), + RRuleType::Validated(rrule) => { + RRuleType::Unvalidated(to_unvalidated(&rrule).by_weekday(weekdays)) + } + } + } + + pub fn by_hour(self, hours: Vec) -> Self { + match self { + RRuleType::Unvalidated(rrule) => RRuleType::Unvalidated(rrule.by_hour(hours)), + RRuleType::Validated(rrule) => RRuleType::Unvalidated(to_unvalidated(&rrule).by_hour(hours)), + } + } + + pub fn by_minute(self, minutes: Vec) -> Self { + match self { + RRuleType::Unvalidated(rrule) => RRuleType::Unvalidated(rrule.by_minute(minutes)), + RRuleType::Validated(rrule) => { + RRuleType::Unvalidated(to_unvalidated(&rrule).by_minute(minutes)) + } + } + } + + pub fn by_second(self, seconds: Vec) -> Self { + match self { + RRuleType::Unvalidated(rrule) => RRuleType::Unvalidated(rrule.by_second(seconds)), + RRuleType::Validated(rrule) => { + RRuleType::Unvalidated(to_unvalidated(&rrule).by_second(seconds)) + } + } + } + + pub fn by_month_day(self, days: Vec) -> Self { + match self { + RRuleType::Unvalidated(rrule) => RRuleType::Unvalidated(rrule.by_month_day(days)), + RRuleType::Validated(rrule) => { + RRuleType::Unvalidated(to_unvalidated(&rrule).by_month_day(days)) + } + } + } + + pub fn by_set_pos(self, poses: Vec) -> Self { + match self { + RRuleType::Unvalidated(rrule) => RRuleType::Unvalidated(rrule.by_set_pos(poses)), + RRuleType::Validated(rrule) => { + RRuleType::Unvalidated(to_unvalidated(&rrule).by_set_pos(poses)) + } + } + } + + pub fn by_month(self, months: &[Month]) -> Self { + match self { + RRuleType::Unvalidated(rrule) => RRuleType::Unvalidated(rrule.by_month(months)), + RRuleType::Validated(rrule) => { + RRuleType::Unvalidated(to_unvalidated(&rrule).by_month(months)) + } + } + } + + pub fn by_week_no(self, week_numbers: Vec) -> Self { + match self { + RRuleType::Unvalidated(rrule) => RRuleType::Unvalidated(rrule.by_week_no(week_numbers)), + RRuleType::Validated(rrule) => { + RRuleType::Unvalidated(to_unvalidated(&rrule).by_week_no(week_numbers)) + } + } + } + + pub fn by_year_day(self, days: Vec) -> Self { + match self { + RRuleType::Unvalidated(rrule) => RRuleType::Unvalidated(rrule.by_year_day(days)), + RRuleType::Validated(rrule) => { + RRuleType::Unvalidated(to_unvalidated(&rrule).by_year_day(days)) + } + } + } + + pub fn week_start(self, day: Weekday) -> Self { + match self { + RRuleType::Unvalidated(rrule) => RRuleType::Unvalidated(rrule.week_start(day)), + RRuleType::Validated(rrule) => RRuleType::Unvalidated(to_unvalidated(&rrule).week_start(day)), + } + } + + pub fn until(self, until: DateTime) -> Self { + match self { + RRuleType::Unvalidated(rrule) => RRuleType::Unvalidated(rrule.until(until)), + RRuleType::Validated(rrule) => RRuleType::Unvalidated(to_unvalidated(&rrule).until(until)), + } + } + + pub fn validate(&self, dt_start: DateTime) -> Result> { + match self { + RRuleType::Unvalidated(rrule) => { + let rrule = rrule + .clone() + .validate(dt_start) + .map_err(|e| napi::Error::new(napi::Status::GenericFailure, e))?; + Ok(rrule) + } + RRuleType::Validated(rrule) => Ok(rrule.clone()), + } + } + + pub fn to_string(&self) -> String { + match self { + RRuleType::Unvalidated(rrule) => rrule.to_string(), + RRuleType::Validated(rrule) => rrule.to_string(), + } + } +} + #[napi(js_name = "RRule")] pub struct JsRRule { - rrule: RRule, + rrule: RRuleType, } #[napi] impl JsRRule { #[napi(constructor)] pub fn new(frequency: JsFrequency) -> Self { - let rrule = RRule::new(map_js_frequency(frequency)); + let rrule = RRuleType::Unvalidated(RRule::new(map_js_frequency(frequency))); JsRRule { rrule } } @@ -297,13 +561,7 @@ impl JsRRule { } pub fn validate(&self, dt_start: DateTime) -> napi::Result { - return Ok( - self - .rrule - .clone() - .validate(dt_start) - .map_err(|e| napi::Error::new(napi::Status::GenericFailure, e))?, - ); + return Ok(self.rrule.validate(dt_start)?); } } @@ -378,19 +636,45 @@ impl JsRRuleSet { Ok(String::from(self.tz.name())) } - /*#[napi(ts_return_type="RRule[]")] + #[napi(ts_return_type = "RRule[]")] pub fn get_rrules(&self, env: Env) -> napi::Result { - let mut arr = env.create_array(0).unwrap(); + let mut arr = env.create_array(0)?; let rrules = self.rrule_set.get_rrule(); for rrule in rrules.iter() { arr.insert(JsRRule { - freq: map_rust_frequency(rrule.get_freq()) - }).unwrap(); + rrule: RRuleType::Validated(rrule.clone()), + })? + } + + Ok(arr) + } + + #[napi(ts_return_type = "RRule[]")] + pub fn get_exrules(&self, env: Env) -> napi::Result { + let mut arr = env.create_array(0)?; + let rrules = self.rrule_set.get_exrule(); + + for rrule in rrules.iter() { + arr.insert(JsRRule { + rrule: RRuleType::Validated(rrule.clone()), + })? } Ok(arr) - }*/ + } + + #[napi(ts_return_type = "number[]")] + pub fn get_exdates(&self, env: Env) -> napi::Result { + let mut arr = env.create_array(0)?; + let dates = self.rrule_set.get_exdate(); + + for date in dates.iter() { + arr.insert(date.timestamp_millis())? + } + + Ok(arr) + } fn is_after(&self, timestamp: i64, after_timestamp: i64, inclusive: Option) -> bool { let inclusive = inclusive.unwrap_or(false); @@ -488,15 +772,6 @@ fn map_js_frequency(freq: JsFrequency) -> Frequency { } } -/*fn map_rust_rrule (rrule: RRule) -> JsRRule { - JsRRule { - freq: map_rust_frequency(rrule.get_freq()), - interval: Some(rrule.get_interval()), - count: rrule.get_count(), - by_weekday: - } -}*/ - fn map_js_weekday(weekday: JsWeekday) -> Weekday { match weekday { JsWeekday::Monday => Weekday::Mon,