Skip to content

Commit 648a446

Browse files
committed
fix: do not show deleted recurrences of events
Resolves #418, #530
1 parent 4af69c5 commit 648a446

4 files changed

Lines changed: 87 additions & 13 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
BEGIN:VCALENDAR
2+
PRODID:-//Google Inc//Google Calendar 70.9054//EN
3+
VERSION:2.0
4+
CALSCALE:GREGORIAN
5+
METHOD:PUBLISH
6+
X-WR-CALNAME:test-ical
7+
X-WR-TIMEZONE:Europe/Warsaw
8+
BEGIN:VTIMEZONE
9+
TZID:Europe/Warsaw
10+
X-LIC-LOCATION:Europe/Warsaw
11+
BEGIN:DAYLIGHT
12+
TZOFFSETFROM:+0100
13+
TZOFFSETTO:+0200
14+
TZNAME:GMT+2
15+
DTSTART:19700329T020000
16+
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
17+
END:DAYLIGHT
18+
BEGIN:STANDARD
19+
TZOFFSETFROM:+0200
20+
TZOFFSETTO:+0100
21+
TZNAME:GMT+1
22+
DTSTART:19701025T030000
23+
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
24+
END:STANDARD
25+
END:VTIMEZONE
26+
BEGIN:VEVENT
27+
DTSTART;TZID=Europe/Warsaw:20240927T180000
28+
DTEND;TZID=Europe/Warsaw:20240927T200000
29+
RRULE:FREQ=DAILY
30+
EXDATE;TZID=Europe/Warsaw:20240928T180000
31+
DTSTAMP:20240928T171122Z
32+
UID:2g8p75vbi3ojunbnejha28opic@google.com
33+
CREATED:20240928T170958Z
34+
LAST-MODIFIED:20240928T171047Z
35+
LOCATION:Rynek Główny\, 31-422 Kraków\, Польша
36+
SEQUENCE:1
37+
STATUS:CONFIRMED
38+
SUMMARY:recurring
39+
TRANSP:OPAQUE
40+
END:VEVENT
41+
END:VCALENDAR

src/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export const editContextKey = "editContext";
77
export const dateRangeContextKey = "dateRangeContext";
88
export const errorContextKey = "errorContext";
99
export const defaultDayFormat = "YYYY-MM-DD";
10-
export const originalRecurrenceDayKeyFormat = "YYYY-MM-DD";
10+
export const icalDayKeyFormat = "YYYY-MM-DD";
1111
export const defaultDayFormatForLuxon = "yyyy-MM-dd";
1212
export const clockSeparator = "--";
1313
export const defaultDurationMinutes = 30;

src/remote-calendars.test.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@ function getMockRequest(): Mock {
2626
return jest.requireMock("obsidian").request;
2727
}
2828

29-
function createStore() {
29+
function createStore({ visibleDays = [moment("2024-09-26")] } = {}) {
3030
const syncTrigger = writable({});
3131

3232
const store = useDayToEventOccurences({
3333
isOnline: writable(true),
34-
visibleDays: writable([moment("2024-09-26")]),
34+
visibleDays: writable(visibleDays),
3535
syncTrigger,
3636
settings: writable({
3737
...defaultSettingsForTests,
@@ -92,7 +92,30 @@ test("Falls back on previous values if fetching a calendar fails", async () => {
9292
});
9393
});
9494

95-
test.todo("Deleted recurrences don't show up as tasks");
95+
test("Deleted recurrences don't show up as tasks", async () => {
96+
getMockRequest().mockReturnValue(
97+
getIcalFixture("google-recurring-with-exception-and-location"),
98+
);
99+
100+
const { store } = createStore({
101+
visibleDays: [moment("2024-09-27"), moment("2024-09-28")],
102+
});
103+
104+
await waitFor(() => {
105+
expect(get(store)).toEqual({
106+
"2024-09-27": {
107+
noTime: [],
108+
withTime: [
109+
expect.objectContaining({
110+
summary: "recurring",
111+
}),
112+
],
113+
},
114+
});
115+
});
116+
});
117+
118+
test.todo("Location gets passed to an event");
96119

97120
test.todo("Yearly recurrences do not show up every month");
98121

src/util/ical.ts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,7 @@ import moment, { type Moment } from "moment";
22
import { tz } from "moment-timezone";
33
import ical, { type AttendeePartStat } from "node-ical";
44

5-
import {
6-
fallbackPartStat,
7-
noTitle,
8-
originalRecurrenceDayKeyFormat,
9-
} from "../constants";
5+
import { fallbackPartStat, noTitle, icalDayKeyFormat } from "../constants";
106
import type { RemoteTask, WithTime } from "../task-types";
117
import type { WithIcalConfig } from "../types";
128

@@ -25,14 +21,24 @@ export function canHappenAfter(icalEvent: ical.VEvent, date: Date) {
2521
);
2622
}
2723

28-
function hasRecurrenceOverride(icalEvent: ical.VEvent, date: Date) {
24+
function hasRecurrenceOverrideForDate(icalEvent: ical.VEvent, date: Date) {
2925
if (!icalEvent.recurrences) {
3026
return false;
3127
}
3228

33-
const dateKey = moment(date).format(originalRecurrenceDayKeyFormat);
29+
return Object.hasOwn(icalEvent.recurrences, getIcalDayKey(date));
30+
}
31+
32+
function getIcalDayKey(date: Date) {
33+
return moment(date).format(icalDayKeyFormat);
34+
}
35+
36+
function hasExceptionForDate(icalEvent: ical.VEvent, date: Date) {
37+
if (!icalEvent.exdate) {
38+
return false;
39+
}
3440

35-
return Object.hasOwn(icalEvent.recurrences, dateKey);
41+
return Object.keys(icalEvent.exdate).includes(getIcalDayKey(date));
3642
}
3743

3844
export function icalEventToTasks(
@@ -53,7 +59,11 @@ export function icalEventToTasks(
5359

5460
const recurrences = icalEvent.rrule
5561
?.between(startOfDay, endOfDay)
56-
.filter((date) => !hasRecurrenceOverride(icalEvent, date))
62+
.filter(
63+
(date) =>
64+
!hasRecurrenceOverrideForDate(icalEvent, date) &&
65+
!hasExceptionForDate(icalEvent, date),
66+
)
5767
.map((date) => icalEventToTask(icalEvent, date));
5868

5969
return [...recurrences, ...recurrenceOverrides];

0 commit comments

Comments
 (0)