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
8 changes: 6 additions & 2 deletions modules/default/calendar/calendarfetcherutils.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,12 @@ const CalendarFetcherUtils = {
// rrule.js returns UTC dates with tzid cleared, so we interpret them in the event's original timezone
return dates.map((date) => {
if (isFullDayEvent) {
// For all-day events, anchor to calendar day in event's timezone
return moment.tz(date, eventTimezone).startOf("day");
// For all-day events, extract UTC date components and interpret as local calendar date
// This prevents timezone offsets from shifting the date to the previous/next day
const utcYear = date.getUTCFullYear();
const utcMonth = date.getUTCMonth();
const utcDate = date.getUTCDate();
return moment.tz([utcYear, utcMonth, utcDate], eventTimezone);
}
// For timed events, preserve the time in the event's original timezone
return moment.tz(date, "UTC").tz(eventTimezone, true);
Expand Down
42 changes: 42 additions & 0 deletions tests/unit/modules/default/calendar/calendar_fetcher_utils_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,5 +111,47 @@ END:VEVENT`);
expect(januaryFirst[0].toISOString(true)).toContain("09:00:00.000+01:00");
expect(julyFirst[0].toISOString(true)).toContain("09:00:00.000+02:00");
});

it("should return correct day-of-week for full-day recurring events across DST transitions", () => {
// Test case for GitHub issue #3976: recurring full-day events showing on wrong day
// This happens when DST transitions change the UTC offset between occurrences
const data = ical.parseICS(`BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART;VALUE=DATE:20251027
DTEND;VALUE=DATE:20251028
RRULE:FREQ=WEEKLY;WKST=SU;COUNT=3
DTSTAMP:20260103T123138Z
UID:[email protected]
SUMMARY:Weekly Monday Event
END:VEVENT
END:VCALENDAR`);

const event = data["[email protected]"];

// Simulate calendar with timezone (e.g., from X-WR-TIMEZONE or user config)
// This is how MagicMirror handles full-day events from calendars with timezones
event.start.tz = "America/Chicago";

const pastMoment = moment("2025-10-01");
const futureMoment = moment("2025-11-30");

// Get moments for the recurring event
const moments = CalendarFetcherUtils.getMomentsFromRecurringEvent(event, pastMoment, futureMoment, 0);

// All occurrences should be on Monday (day() === 1) at midnight
// Oct 27, 2025 - Before DST ends
// Nov 3, 2025 - After DST ends (this was showing as Sunday before the fix)
// Nov 10, 2025 - After DST ends
expect(moments).toHaveLength(3);
expect(moments[0].day()).toBe(1); // Monday
expect(moments[0].format("YYYY-MM-DD")).toBe("2025-10-27");
expect(moments[0].hour()).toBe(0); // Midnight
expect(moments[1].day()).toBe(1); // Monday (not Sunday!)
expect(moments[1].format("YYYY-MM-DD")).toBe("2025-11-03");
expect(moments[1].hour()).toBe(0); // Midnight
expect(moments[2].day()).toBe(1); // Monday
expect(moments[2].format("YYYY-MM-DD")).toBe("2025-11-10");
expect(moments[2].hour()).toBe(0); // Midnight
});
});
});