Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions ics/with-rdate.ics
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Test//Test//EN
BEGIN:VEVENT
UID:rdate-future@test
DTSTART:20200101T100000Z
DTEND:20200101T110000Z
RDATE:20300101T100000Z,20310101T100000Z
SUMMARY:RDATE with future dates - should be kept
END:VEVENT
BEGIN:VEVENT
UID:rdate-past@test
DTSTART:20180101T100000Z
DTEND:20180101T110000Z
RDATE:20190101T100000Z,20200101T100000Z
SUMMARY:RDATE with only past dates - should be dropped
END:VEVENT
BEGIN:VEVENT
UID:rdate-value-date@test
DTSTART:20200601T100000Z
DTEND:20200601T110000Z
RDATE;VALUE=DATE:20300601,20310601
SUMMARY:RDATE with VALUE=DATE format - should be kept
END:VEVENT
BEGIN:VEVENT
UID:rdate-period@test
DTSTART:20200601T100000Z
DTEND:20200601T120000Z
RDATE;VALUE=PERIOD:20300601T100000Z/20300601T120000Z
SUMMARY:RDATE with VALUE=PERIOD format - should be kept
END:VEVENT
END:VCALENDAR
45 changes: 45 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export const icsFilter = (content: string, now: Date, max?: Date): string => {

let keep: boolean = true;
let hasRrule: boolean = false;
let hasRdate: boolean = false;
let rdateKeep: boolean = false;
let hasKeptRecurrenceId: boolean = false;
let rruleKeep: boolean = true;

Expand Down Expand Up @@ -57,6 +59,8 @@ export const icsFilter = (content: string, now: Date, max?: Date): string => {

keep = true;
hasRrule = false;
hasRdate = false;
rdateKeep = false;
hasKeptRecurrenceId = false;
rruleKeep = true;
return;
Expand Down Expand Up @@ -142,6 +146,14 @@ export const icsFilter = (content: string, now: Date, max?: Date): string => {
return;
}

if (hasRdate && rdateKeep) {
if (isDevRun) {
console.log("DTEND lower than now, but rdate keeps it:", dtEnd, nowStr, "-->", line);
}

return;
}

if (hasKeptRecurrenceId) {
if (isDevRun) {
console.log("DTEND lower than now, but recurrenceId keeps it:", dtEnd, nowStr, "-->", line);
Expand Down Expand Up @@ -188,6 +200,39 @@ export const icsFilter = (content: string, now: Date, max?: Date): string => {
return;
}

if (lineUpper.startsWith("RDATE")) {
const value: string | null = extractValue(line);
if (!value) {
return;
}

hasRdate = true;

for (const dateRaw of value.split(",")) {
const date: string | null = normalizeICSDateStr(dateRaw.trim());
if (!date) {
continue;
}

if (date >= nowStr && (!maxStr || date <= maxStr)) {
rdateKeep = true;
keep = true;

if (isDevRun) {
console.log("RDATE date in range:", date, "-->", line);
}

break;
}
}

if (isDevRun && !rdateKeep) {
console.log("RDATE: no dates in range", "-->", line);
}

return;
}

if (lineUpper.startsWith("RECURRENCE-ID")) {
const value: string | null = extractValue(line);
const dtRecurrence: string | null = normalizeICSDateStr(value);
Expand Down
37 changes: 37 additions & 0 deletions src/tests/rdate-support.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import assert from "node:assert";
import { readFileSync } from "node:fs";
import { describe, test } from "node:test";

import { icsFilter } from "../index.js";

const content: string = readFileSync("./ics/with-rdate.ics", "utf-8");
const now: Date = new Date();
const max: Date = new Date(now.getTime() + 365 * 24 * 60 * 60 * 1000); // 1 year from now
const filteredContent: string = icsFilter(content, now);
const filteredMaxContent: string = icsFilter(content, now, max);

describe("icsFilter on ics content with RDATE properties", () => {
test("should keep event with RDATE dates in the future", () => {
assert.ok(filteredContent.includes("rdate-future@test"), "Event with future RDATE dates should be kept");
});

test("should drop event with only past RDATE dates", () => {
assert.ok(!filteredContent.includes("rdate-past@test"), "Event with only past RDATE dates should be dropped");
});

test("should keep event with RDATE;VALUE=DATE format dates in the future", () => {
assert.ok(filteredContent.includes("rdate-value-date@test"), "Event with future RDATE;VALUE=DATE dates should be kept");
});

test("should keep event with RDATE;VALUE=PERIOD format dates in the future", () => {
assert.ok(filteredContent.includes("rdate-period@test"), "Event with future RDATE;VALUE=PERIOD dates should be kept");
});
});

describe("icsFilter on ics content with RDATE properties and max limit", () => {
test("should drop events whose RDATE dates are all beyond max", () => {
assert.ok(!filteredMaxContent.includes("rdate-future@test"), "Event with RDATE dates beyond max should be dropped");
assert.ok(!filteredMaxContent.includes("rdate-value-date@test"), "Event with RDATE;VALUE=DATE dates beyond max should be dropped");
assert.ok(!filteredMaxContent.includes("rdate-period@test"), "Event with RDATE;VALUE=PERIOD dates beyond max should be dropped");
});
});