Skip to content

Commit e0e7a24

Browse files
authored
feat: add date support (#503)
1 parent 5567c35 commit e0e7a24

29 files changed

+1305
-466
lines changed

jest.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ export default {
1111
},
1212
],
1313
},
14+
setupFilesAfterEnv: ['./jest.setup.ts'],
1415
} satisfies JestConfigWithTsJest;
1516
/* eslint-enable import-x/no-default-export -- Required by Jest */

jest.setup.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { iterableEquality } from '@jest/expect-utils';
2+
3+
interface ToPlain {
4+
toPlain(): unknown;
5+
}
6+
7+
declare global {
8+
// eslint-disable-next-line @typescript-eslint/no-namespace -- Required for jest
9+
namespace jest {
10+
interface Matchers<R> {
11+
toEqualPlain<E extends ToPlain>(expected: E | undefined): R;
12+
toEqualPlain<E extends ToPlain>(expected: E[]): R;
13+
}
14+
}
15+
}
16+
17+
expect.extend({
18+
toEqualPlain(
19+
received: ToPlain[] | ToPlain | undefined,
20+
expected: ToPlain[] | ToPlain | undefined,
21+
) {
22+
let pass = false;
23+
let expectedPlain: unknown;
24+
let receivedPlain: unknown;
25+
26+
if (Array.isArray(received) && Array.isArray(expected)) {
27+
receivedPlain = received.map((r) => r.toPlain());
28+
expectedPlain = expected.map((e) => e.toPlain());
29+
30+
pass = this.equals(receivedPlain, expectedPlain, [iterableEquality]);
31+
} else if (!Array.isArray(received) && !Array.isArray(expected)) {
32+
receivedPlain = received?.toPlain();
33+
expectedPlain = expected?.toPlain();
34+
35+
pass = this.equals(receivedPlain, expectedPlain);
36+
} else {
37+
throw new Error(
38+
'Both received and expected values must be of the same type, either ToPlain or ToPlain[].',
39+
);
40+
}
41+
42+
return {
43+
message: () =>
44+
`Expected plain does not match received plain:\r\n\r\n${this.utils.printDiffOrStringify(expectedPlain, receivedPlain, 'Expected', 'Received', true)}`,
45+
pass,
46+
};
47+
},
48+
});

lib/js/rrule_set.rs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::rrule::datetime::DateTime;
33
use crate::rrule::dtstart::DtStart;
44
use crate::rrule::exdate::ExDate;
55
use crate::rrule::rdate::RDate;
6+
use crate::rrule::value_type::ValueType;
67
use crate::rrule::{rrule, rrule_set};
78
use napi::bindgen_prelude::{Array, Reference, SharedReference};
89
use napi::Env;
@@ -20,6 +21,7 @@ impl RRuleSet {
2021
pub fn new(
2122
dtstart: i64,
2223
tzid: Option<String>,
24+
dtstart_value: Option<String>,
2325
#[napi(ts_arg_type = "(readonly RRule[]) | undefined | null")] rrules: Option<Vec<&RRule>>,
2426
#[napi(ts_arg_type = "(readonly RRule[]) | undefined | null")] exrules: Option<Vec<&RRule>>,
2527
#[napi(ts_arg_type = "(readonly number[]) | undefined | null")] exdates: Option<Vec<i64>>,
@@ -34,7 +36,12 @@ impl RRuleSet {
3436
None => None,
3537
};
3638

37-
let dtstart = DtStart::new(dtstart.into(), tzid)
39+
let dtstat_value = dtstart_value
40+
.map(|value| value.parse::<ValueType>())
41+
.transpose()
42+
.map_err(|e| napi::Error::new(napi::Status::GenericFailure, e))?;
43+
44+
let dtstart = DtStart::new(dtstart.into(), tzid, dtstat_value)
3845
.map_err(|e| napi::Error::new(napi::Status::GenericFailure, e))?;
3946

4047
let rrules: Vec<rrule::RRule> = rrules
@@ -63,9 +70,13 @@ impl RRuleSet {
6370

6471
let rrule_set = rrule_set::RRuleSet::new(dtstart)
6572
.set_rrules(rrules)
73+
.map_err(|e| napi::Error::new(napi::Status::GenericFailure, e))?
6674
.set_exrules(exrules)
75+
.map_err(|e| napi::Error::new(napi::Status::GenericFailure, e))?
6776
.set_exdates(exdates)
68-
.set_rdates(rdates);
77+
.map_err(|e| napi::Error::new(napi::Status::GenericFailure, e))?
78+
.set_rdates(rdates)
79+
.map_err(|e| napi::Error::new(napi::Status::GenericFailure, e))?;
6980

7081
Ok(Self { rrule_set })
7182
}
@@ -83,7 +94,7 @@ impl RRuleSet {
8394

8495
#[napi(getter)]
8596
pub fn dtstart(&self) -> napi::Result<i64> {
86-
Ok(self.rrule_set.dtstart().datetime().into())
97+
Ok(self.rrule_set.dtstart().value().into())
8798
}
8899

89100
#[napi(getter, ts_return_type = "RRule[]")]
@@ -120,7 +131,7 @@ impl RRuleSet {
120131
.map_err(|e| napi::Error::new(napi::Status::GenericFailure, e))?;
121132

122133
for datetime in datetimes.iter() {
123-
let datetime = datetime.with_timezone(&self.rrule_set.dtstart().timezone());
134+
let datetime = datetime.with_timezone(&self.rrule_set.dtstart().derive_timezone());
124135
let datetime = DateTime::from(&datetime);
125136

126137
exdates.push((&datetime).into());
@@ -140,7 +151,7 @@ impl RRuleSet {
140151
.map_err(|e| napi::Error::new(napi::Status::GenericFailure, e))?;
141152

142153
for datetime in datetimes.iter() {
143-
let datetime = datetime.with_timezone(&self.rrule_set.dtstart().timezone());
154+
let datetime = datetime.with_timezone(&self.rrule_set.dtstart().derive_timezone());
144155
let datetime = DateTime::from(&datetime);
145156

146157
rdates.push((&datetime).into());
@@ -208,7 +219,7 @@ impl RRuleSet {
208219
inclusive: Option<bool>,
209220
) -> napi::Result<Array<'a>> {
210221
let mut arr = env.create_array(0).unwrap();
211-
let timezone = self.rrule_set.dtstart().timezone();
222+
let timezone = self.rrule_set.dtstart().derive_timezone();
212223
let after_timestamp = DateTime::from(after_datetime)
213224
.to_datetime(&timezone)
214225
.map_err(|e| napi::Error::new(napi::Status::GenericFailure, e))?

lib/rrule.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@ pub mod n_weekday;
99
pub mod rdate;
1010
pub mod rrule;
1111
pub mod rrule_set;
12+
pub mod time;
13+
pub mod value_type;
1214
pub mod weekday;

0 commit comments

Comments
 (0)