Skip to content

Commit 7a87ea4

Browse files
authored
feat: introduce ExDate and RDate objects (#544)
1 parent e0e7a24 commit 7a87ea4

File tree

20 files changed

+527
-106
lines changed

20 files changed

+527
-106
lines changed

lib/js.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
pub mod exdate;
12
pub mod frequency;
23
pub mod month;
34
pub mod n_weekday;
5+
pub mod rdate;
46
pub mod rrule;
57
pub mod rrule_set;
68
pub mod weekday;

lib/js/exdate.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
use crate::rrule::{
2+
datetime::{self},
3+
exdate,
4+
};
5+
use napi_derive::napi;
6+
7+
#[napi(js_name = "ExDate")]
8+
pub struct ExDate {
9+
exdate: exdate::ExDate,
10+
}
11+
12+
#[napi]
13+
impl ExDate {
14+
#[napi(constructor)]
15+
pub fn new(
16+
#[napi(ts_arg_type = "readonly number[]")] datetimes: Vec<i64>,
17+
tzid: Option<String>,
18+
) -> napi::Result<Self> {
19+
let tzid: Option<chrono_tz::Tz> = match tzid {
20+
Some(tzid) => Some(
21+
tzid
22+
.parse()
23+
.map_err(|e| napi::Error::new(napi::Status::GenericFailure, e))?,
24+
),
25+
None => None,
26+
};
27+
28+
let exdate = exdate::ExDate::new(
29+
datetimes
30+
.into_iter()
31+
.map(|datetime| datetime::DateTime::from(datetime))
32+
.collect(),
33+
tzid,
34+
None,
35+
)
36+
.map_err(|e| napi::Error::new(napi::Status::GenericFailure, e))?;
37+
38+
Ok(Self { exdate })
39+
}
40+
41+
#[napi(getter)]
42+
pub fn values(&self) -> napi::Result<Vec<i64>> {
43+
Ok(self.exdate.values().iter().map(|dt| dt.into()).collect())
44+
}
45+
46+
#[napi(getter)]
47+
pub fn tzid(&self) -> napi::Result<Option<String>> {
48+
Ok(self.exdate.tzid().and_then(|tzid| Some(tzid.to_string())))
49+
}
50+
}
51+
52+
impl Into<exdate::ExDate> for &ExDate {
53+
fn into(self) -> exdate::ExDate {
54+
self.exdate.clone()
55+
}
56+
}
57+
58+
impl From<exdate::ExDate> for ExDate {
59+
fn from(exdate: exdate::ExDate) -> Self {
60+
Self { exdate }
61+
}
62+
}

lib/js/rdate.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
use crate::rrule::{
2+
datetime::{self},
3+
rdate,
4+
};
5+
use napi_derive::napi;
6+
7+
#[napi(js_name = "RDate")]
8+
pub struct RDate {
9+
rdate: rdate::RDate,
10+
}
11+
12+
#[napi]
13+
impl RDate {
14+
#[napi(constructor)]
15+
pub fn new(
16+
#[napi(ts_arg_type = "readonly number[]")] datetimes: Vec<i64>,
17+
tzid: Option<String>,
18+
) -> napi::Result<Self> {
19+
let tzid: Option<chrono_tz::Tz> = match tzid {
20+
Some(tzid) => Some(
21+
tzid
22+
.parse()
23+
.map_err(|e| napi::Error::new(napi::Status::GenericFailure, e))?,
24+
),
25+
None => None,
26+
};
27+
28+
let rdate = rdate::RDate::new(
29+
datetimes
30+
.into_iter()
31+
.map(|datetime| datetime::DateTime::from(datetime))
32+
.collect(),
33+
tzid,
34+
None,
35+
)
36+
.map_err(|e| napi::Error::new(napi::Status::GenericFailure, e))?;
37+
38+
Ok(Self { rdate })
39+
}
40+
41+
#[napi(getter)]
42+
pub fn values(&self) -> napi::Result<Vec<i64>> {
43+
Ok(self.rdate.values().iter().map(|dt| dt.into()).collect())
44+
}
45+
46+
#[napi(getter)]
47+
pub fn tzid(&self) -> napi::Result<Option<String>> {
48+
Ok(self.rdate.tzid().and_then(|tzid| Some(tzid.to_string())))
49+
}
50+
}
51+
52+
impl Into<rdate::RDate> for &RDate {
53+
fn into(self) -> rdate::RDate {
54+
self.rdate.clone()
55+
}
56+
}
57+
58+
impl From<rdate::RDate> for RDate {
59+
fn from(rdate: rdate::RDate) -> Self {
60+
Self { rdate }
61+
}
62+
}

lib/js/rrule_set.rs

Lines changed: 27 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1+
use super::exdate::ExDate;
2+
use super::rdate::RDate;
13
use super::rrule::RRule;
24
use crate::rrule::datetime::DateTime;
35
use crate::rrule::dtstart::DtStart;
4-
use crate::rrule::exdate::ExDate;
5-
use crate::rrule::rdate::RDate;
66
use crate::rrule::value_type::ValueType;
7-
use crate::rrule::{rrule, rrule_set};
7+
use crate::rrule::{exdate, rdate, rrule, rrule_set};
88
use napi::bindgen_prelude::{Array, Reference, SharedReference};
99
use napi::Env;
1010
use napi_derive::napi;
@@ -24,8 +24,8 @@ impl RRuleSet {
2424
dtstart_value: Option<String>,
2525
#[napi(ts_arg_type = "(readonly RRule[]) | undefined | null")] rrules: Option<Vec<&RRule>>,
2626
#[napi(ts_arg_type = "(readonly RRule[]) | undefined | null")] exrules: Option<Vec<&RRule>>,
27-
#[napi(ts_arg_type = "(readonly number[]) | undefined | null")] exdates: Option<Vec<i64>>,
28-
#[napi(ts_arg_type = "(readonly number[]) | undefined | null")] rdates: Option<Vec<i64>>,
27+
#[napi(ts_arg_type = "(readonly ExDate[]) | undefined | null")] exdates: Option<Vec<&ExDate>>,
28+
#[napi(ts_arg_type = "(readonly RDate[]) | undefined | null")] rdates: Option<Vec<&RDate>>,
2929
) -> napi::Result<Self> {
3030
let tzid: Option<chrono_tz::Tz> = match tzid {
3131
Some(tzid) => Some(
@@ -56,13 +56,13 @@ impl RRuleSet {
5656
.map(|rrule| rrule.into())
5757
.collect();
5858

59-
let exdates: Vec<ExDate> = exdates
59+
let exdates: Vec<exdate::ExDate> = exdates
6060
.unwrap_or_default()
6161
.into_iter()
6262
.map(Into::into)
6363
.collect();
6464

65-
let rdates: Vec<RDate> = rdates
65+
let rdates: Vec<rdate::RDate> = rdates
6666
.unwrap_or_default()
6767
.into_iter()
6868
.map(Into::into)
@@ -121,44 +121,28 @@ impl RRuleSet {
121121
)
122122
}
123123

124-
#[napi(getter, ts_return_type = "number[]")]
125-
pub fn exdates(&self) -> napi::Result<Vec<i64>> {
126-
let mut exdates = Vec::<i64>::new();
127-
128-
for exdate in self.rrule_set.exdates() {
129-
let datetimes = exdate
130-
.to_datetimes(&self.rrule_set.dtstart())
131-
.map_err(|e| napi::Error::new(napi::Status::GenericFailure, e))?;
132-
133-
for datetime in datetimes.iter() {
134-
let datetime = datetime.with_timezone(&self.rrule_set.dtstart().derive_timezone());
135-
let datetime = DateTime::from(&datetime);
136-
137-
exdates.push((&datetime).into());
138-
}
139-
}
140-
141-
Ok(exdates)
124+
#[napi(getter, ts_return_type = "ExDate[]")]
125+
pub fn exdates(&self) -> napi::Result<Vec<ExDate>> {
126+
Ok(
127+
self
128+
.rrule_set
129+
.exdates()
130+
.iter()
131+
.map(|exdate| exdate.clone().into())
132+
.collect(),
133+
)
142134
}
143135

144-
#[napi(getter, ts_return_type = "number[]")]
145-
pub fn rdates(&self) -> napi::Result<Vec<i64>> {
146-
let mut rdates = Vec::<i64>::new();
147-
148-
for rdate in self.rrule_set.rdates() {
149-
let datetimes = rdate
150-
.to_datetimes(&self.rrule_set.dtstart())
151-
.map_err(|e| napi::Error::new(napi::Status::GenericFailure, e))?;
152-
153-
for datetime in datetimes.iter() {
154-
let datetime = datetime.with_timezone(&self.rrule_set.dtstart().derive_timezone());
155-
let datetime = DateTime::from(&datetime);
156-
157-
rdates.push((&datetime).into());
158-
}
159-
}
160-
161-
Ok(rdates)
136+
#[napi(getter, ts_return_type = "RDate[]")]
137+
pub fn rdates(&self) -> napi::Result<Vec<RDate>> {
138+
Ok(
139+
self
140+
.rrule_set
141+
.rdates()
142+
.iter()
143+
.map(|rdate| rdate.clone().into())
144+
.collect(),
145+
)
162146
}
163147

164148
#[napi(factory, ts_return_type = "RRuleSet")]

lib/rrule/exdate.rs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ impl ExDate {
4848
})
4949
}
5050

51+
pub fn tzid(&self) -> &Option<chrono_tz::Tz> {
52+
&self.tzid
53+
}
54+
55+
pub fn values(&self) -> &Vec<DateTime> {
56+
&self.values
57+
}
58+
5159
pub fn value_type(&self) -> &Option<ValueType> {
5260
&self.value_type
5361
}
@@ -62,14 +70,28 @@ impl ExDate {
6270
}
6371
}
6472

73+
pub fn derive_timezone(&self) -> chrono_tz::Tz {
74+
match self.tzid {
75+
Some(tz) => tz.clone(),
76+
None => chrono_tz::Tz::UTC,
77+
}
78+
}
79+
6580
pub fn to_datetimes(
6681
&self,
6782
dtstart: &DtStart,
83+
) -> Result<Vec<chrono::DateTime<chrono_tz::Tz>>, String> {
84+
self.to_datetimes_with_fallback_tzid(dtstart.derive_timezone())
85+
}
86+
87+
pub fn to_datetimes_with_fallback_tzid(
88+
&self,
89+
tzid: chrono_tz::Tz,
6890
) -> Result<Vec<chrono::DateTime<chrono_tz::Tz>>, String> {
6991
self
7092
.values
7193
.iter()
72-
.map(|datetime| datetime.to_datetime(&self.tzid.unwrap_or(dtstart.derive_timezone())))
94+
.map(|datetime| datetime.to_datetime(&self.tzid.unwrap_or(tzid)))
7395
.collect()
7496
}
7597

@@ -99,10 +121,10 @@ impl ExDate {
99121
Value::Single(value) => value,
100122
_ => return Err("Invalid EXDATE value".to_string()),
101123
};
102-
let datetimes: Result<Vec<DateTime>, String> = datetimes
124+
let datetimes = datetimes
103125
.split(',')
104126
.map(|date| date.parse::<DateTime>())
105-
.collect();
127+
.collect::<Result<Vec<DateTime>, String>>()?;
106128

107129
let tzid = match property.parameters().get("TZID") {
108130
Some(value) => {
@@ -126,7 +148,7 @@ impl ExDate {
126148
None => None,
127149
};
128150

129-
Self::new(datetimes?, tzid, value_type)
151+
Self::new(datetimes, tzid, value_type)
130152
}
131153
}
132154

lib/rrule/rdate.rs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,25 @@ impl RDate {
4848
})
4949
}
5050

51+
pub fn tzid(&self) -> &Option<chrono_tz::Tz> {
52+
&self.tzid
53+
}
54+
55+
pub fn values(&self) -> &Vec<DateTime> {
56+
&self.values
57+
}
58+
5159
pub fn value_type(&self) -> &Option<ValueType> {
5260
&self.value_type
5361
}
5462

63+
pub fn derive_timezone(&self) -> chrono_tz::Tz {
64+
match self.tzid {
65+
Some(tz) => tz.clone(),
66+
None => chrono_tz::Tz::UTC,
67+
}
68+
}
69+
5570
pub fn derive_value_type(&self) -> Option<ValueType> {
5671
if self.value_type.is_some() {
5772
self.value_type.clone()
@@ -65,11 +80,18 @@ impl RDate {
6580
pub fn to_datetimes(
6681
&self,
6782
dtstart: &DtStart,
83+
) -> Result<Vec<chrono::DateTime<chrono_tz::Tz>>, String> {
84+
self.to_datetimes_with_fallback_tzid(dtstart.derive_timezone())
85+
}
86+
87+
pub fn to_datetimes_with_fallback_tzid(
88+
&self,
89+
tzid: chrono_tz::Tz,
6890
) -> Result<Vec<chrono::DateTime<chrono_tz::Tz>>, String> {
6991
self
7092
.values
7193
.iter()
72-
.map(|datetime| datetime.to_datetime(&self.tzid.unwrap_or(dtstart.derive_timezone())))
94+
.map(|datetime| datetime.to_datetime(&self.tzid.unwrap_or(tzid)))
7395
.collect()
7496
}
7597

@@ -103,10 +125,10 @@ impl RDate {
103125
Value::Single(value) => value,
104126
_ => return Err("Invalid RDATE value".to_string()),
105127
};
106-
let datetimes: Result<Vec<DateTime>, String> = datetimes
128+
let datetimes = datetimes
107129
.split(',')
108130
.map(|date| date.parse::<DateTime>())
109-
.collect();
131+
.collect::<Result<Vec<DateTime>, String>>()?;
110132

111133
let tzid = match property.parameters().get("TZID") {
112134
Some(value) => {
@@ -130,7 +152,7 @@ impl RDate {
130152
None => None,
131153
};
132154

133-
Self::new(datetimes?, tzid, value_type)
155+
Self::new(datetimes, tzid, value_type)
134156
}
135157
}
136158

src/dtstart.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,7 @@ export class DtStart<
2323
public readonly datetime: DT;
2424
public readonly tzid?: string;
2525

26-
public constructor(
27-
datetime: DateTime<Time> | DateTime<undefined>,
28-
tzid?: string,
29-
);
26+
public constructor(datetime: DT, tzid?: string);
3027
public constructor(options: DtStartOptions<DT>);
3128
public constructor(
3229
datetimeOrOptions: DT | DtStartOptions<DT>,
@@ -37,7 +34,7 @@ export class DtStart<
3734
this.tzid = datetimeOrOptions.tzid;
3835
} else {
3936
this.datetime = datetimeOrOptions;
40-
this.tzid = tzid!;
37+
this.tzid = tzid;
4138
}
4239
}
4340

0 commit comments

Comments
 (0)