Repository: MagicMirrorOrg/MagicMirror
Pull request #3782: Fix calendar rrule until where event is fullday but rrule until has a non-0 time
Author: login: sdetweil
Created: 2025-05-09T21:21:12Z | Merged: 2025-05-12T21:39:36Z

This fixes #3781 

tests supplied

see https://forum.magicmirror.builders/topic/19637/issue-with-outlook-recurring-events

Linked issue #3781: [Bug] calendar client sets fullday repeating until with a timezone offset, rrule excludes the exact day
### Environment

### SYSTEM:   manufacturer: System manufacturer; model: System Product Name; virtual: false
### OS:       platform: linux; distro: Ubuntu; release: 22.04.5 LTS; arch: x64; kernel: 5.15.0-138-generic
### VERSIONS: electron: 35.1.2; used node: 22.15.0; installed node: 22.15.0; npm: 10.9.2; pm2: 5.4.3
### OTHER:    timeZone: America/Chicago; ELECTRON_ENABLE_GPU: undefined 

MagicMirror 2.31.0

### Which start option are you using?

npm run start

### Are you using PM2?

No

### Module

calendar

### Have you tried disabling other modules?

- [x] Yes
- [x] No

### Have you searched if someone else has already reported the issue on the forum or in the issues?

- [x] Yes

### What did you do?

reference https://forum.magicmirror.builders/topic/19637/issue-with-outlook-recurring-events
<details>
<summary>Configuration</summary>

```
		{
			module: "calendar",
			position: "bottom_bar",
			config: {
				hideDuplicates: false,
				maximumEntries: 100,
				calendars: [
					{
						maximumEntries: 100,
						url: "http://localhost:8080/xxx.ics"
					}
				]
			}
		}
```
</details>
xxx.ics

```
BEGIN:VCALENDAR
BEGIN:VEVENT
DESCRIPTION:\n
RRULE:FREQ=YEARLY;UNTIL=20250504T230000Z;INTERVAL=1;BYMONTHDAY=5;BYMONTH=5
UID:040000008200E00074C5B7101A82E00800000000DAEF6ED30D9FDA01000000000000000
 010000000D37F812F0777844A93E97B96AD2D278B
SUMMARY:Person A's Birthday
DTSTART;VALUE=DATE:20250505
DTEND;VALUE=DATE:20250506
CLASS:PUBLIC
PRIORITY:5
DTSTAMP:20250428T133000Z
TRANSP:TRANSPARENT
STATUS:CONFIRMED
SEQUENCE:0
LOCATION:
X-MICROSOFT-CDO-APPT-SEQUENCE:0
X-MICROSOFT-CDO-BUSYSTATUS:FREE
X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
X-MICROSOFT-CDO-IMPORTANCE:1
X-MICROSOFT-CDO-INSTTYPE:1
X-MICROSOFT-DONOTFORWARDMEETING:FALSE
X-MICROSOFT-DISALLOW-COUNTER:FALSE
X-MICROSOFT-REQUESTEDATTENDANCEMODE:DEFAULT
X-MICROSOFT-ISRESPONSEREQUESTED:FALSE
END:VEVENT
END:VCALENDAR
```

Steps to reproduce the issue:

just run npm start , the event is not displayed, but should be

### What did you expect to happen?

the event to be displayed on May 5

### What actually happened?

no event was shown 

### Additional comments

_No response_

### Participation

- [ ] I am willing to submit a pull request for this change.
Linked issue comments: user: login: plebcity
created_at: 2025-06-09T06:59:35Z
body: Is this issue fixed right now with the merge of https://github.com/MagicMirrorOrg/MagicMirror/pull/3806 ?
author_association: CONTRIBUTOR

Diff:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 752b451ba8..37cd41cf90 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,6 +23,10 @@ planned for 2025-07-01
 - [workflow] Run linter und spellcheck with LTS node version (#3767)
 - [workflow] Split "Run test" step into two steps for more clarity (#3767)
 
+### Fixed
+
+- [calendar] fix fullday event rrule until with timezone offset (#3781)
+
 ### Updated
 
 - [core] Update dependencies
diff --git a/modules/default/calendar/calendarfetcherutils.js b/modules/default/calendar/calendarfetcherutils.js
index df567af4f6..c61fd33034 100644
--- a/modules/default/calendar/calendarfetcherutils.js
+++ b/modules/default/calendar/calendarfetcherutils.js
@@ -293,6 +293,13 @@ const CalendarFetcherUtils = {
 
 					event.start = rule.options.dtstart;
 
+					// if until is set, and its a full day event, force the time to midnight. rrule gets confused with non-00 offset
+					// looks like MS Outlook sets the until time incorrectly for fullday events
+					if ((rule.options.until !== undefined) && CalendarFetcherUtils.isFullDayEvent(event)) {
+						Log.debug("fixup rrule until");
+						rule.options.until = new Date(new Date(moment(rule.options.until).startOf("day").add(1, "day")).getTime());
+					}
+
 					Log.debug("fix rrule start=", rule.options.dtstart);
 					Log.debug("event before rrule.between=", JSON.stringify(event, null, 2), "exdates=", event.exdate);
 					// fixup the exdate and recurrence date to local time too for post between() handling
diff --git a/tests/configs/modules/calendar/fullday_until.js b/tests/configs/modules/calendar/fullday_until.js
new file mode 100644
index 0000000000..6713d62441
--- /dev/null
+++ b/tests/configs/modules/calendar/fullday_until.js
@@ -0,0 +1,27 @@
+let config = {
+	address: "0.0.0.0",
+	ipWhitelist: [],
+	timeFormat: 12,
+
+	modules: [
+		{
+			module: "calendar",
+			position: "bottom_bar",
+			config: {
+				hideDuplicates: false,
+				maximumEntries: 100,
+				calendars: [
+					{
+						maximumEntries: 100,
+						url: "http://localhost:8080/tests/mocks/fullday_until.ics"
+					}
+				]
+			}
+		}
+	]
+};
+
+/*************** DO NOT EDIT THE LINE BELOW ***************/
+if (typeof module !== "undefined") {
+	module.exports = config;
+}
diff --git a/tests/electron/modules/calendar_spec.js b/tests/electron/modules/calendar_spec.js
index 0f8ee5803b..b819d2dc97 100644
--- a/tests/electron/modules/calendar_spec.js
+++ b/tests/electron/modules/calendar_spec.js
@@ -84,6 +84,10 @@ describe("Calendar module", () => {
 			await helpers.startApplication("tests/configs/modules/calendar/rrule_until.js", "07 Mar 2024 10:38:00 GMT-07:00", [], "America/Los_Angeles");
 			await expect(doTestCount()).resolves.toBe(1);
 		});
+		it("Issue #3781 recurrence rrule until with date only uses timezone offset incorrectly", async () => {
+			await helpers.startApplication("tests/configs/modules/calendar/fullday_until.js", "01 May 2025", [], "America/Los_Angeles");
+			await expect(doTestCount()).resolves.toBe(1);
+		});
 	});
 
 	/*
diff --git a/tests/mocks/fullday_until.ics b/tests/mocks/fullday_until.ics
new file mode 100644
index 0000000000..ffdc293dcd
--- /dev/null
+++ b/tests/mocks/fullday_until.ics
@@ -0,0 +1,28 @@
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+DESCRIPTION:\n
+RRULE:FREQ=YEARLY;UNTIL=20250504T230000Z;INTERVAL=1;BYMONTHDAY=5;BYMONTH=5
+UID:040000008200E00074C5B7101A82E00800000000DAEF6ED30D9FDA01000000000000000
+ 010000000D37F812F0777844A93E97B96AD2D278B
+SUMMARY:Person A's Birthday
+DTSTART;VALUE=DATE:20250505
+DTEND;VALUE=DATE:20250506
+CLASS:PUBLIC
+PRIORITY:5
+DTSTAMP:20250428T133000Z
+TRANSP:TRANSPARENT
+STATUS:CONFIRMED
+SEQUENCE:0
+LOCATION:
+X-MICROSOFT-CDO-APPT-SEQUENCE:0
+X-MICROSOFT-CDO-BUSYSTATUS:FREE
+X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY
+X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
+X-MICROSOFT-CDO-IMPORTANCE:1
+X-MICROSOFT-CDO-INSTTYPE:1
+X-MICROSOFT-DONOTFORWARDMEETING:FALSE
+X-MICROSOFT-DISALLOW-COUNTER:FALSE
+X-MICROSOFT-REQUESTEDATTENDANCEMODE:DEFAULT
+X-MICROSOFT-ISRESPONSEREQUESTED:FALSE
+END:VEVENT
+END:VCALENDAR


Reviews:
user: login: khassel
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-09T21:51:21Z
user: login: sdetweil
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-09T21:53:35Z
user: login: sdetweil
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-09T22:10:47Z
user: login: khassel
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-09T22:17:03Z
user: login: khassel
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-12T21:39:30Z

Review comments:
diff_hunk: @@ -23,10 +23,15 @@ planned for 2025-07-01
 - [workflow] Run linter und spellcheck with LTS node version (#3767)
 - [workflow] Split "Run test" step into two steps for more clarity (#3767)
 
+### Fixed
+
+- [calendar] fix fullday event rrule until with timezone offset (#3781)
+ 
 ### Updated
 
 - [core] Update dependencies
 
+
path: CHANGELOG.md
user: login: khassel
body: I think prettier don't want this additional line.
created_at: 2025-05-09T21:51:21Z
author_association: COLLABORATOR
diff_hunk: @@ -23,10 +23,15 @@ planned for 2025-07-01
 - [workflow] Run linter und spellcheck with LTS node version (#3767)
 - [workflow] Split "Run test" step into two steps for more clarity (#3767)
 
+### Fixed
+
+- [calendar] fix fullday event rrule until with timezone offset (#3781)
+ 
 ### Updated
 
 - [core] Update dependencies
 
+
path: CHANGELOG.md
user: login: sdetweil
body: it was in the source changelog

 i added above it

prettier on system passes
prettier in test run fails
created_at: 2025-05-09T21:53:35Z
author_association: COLLABORATOR
diff_hunk: @@ -23,10 +23,15 @@ planned for 2025-07-01
 - [workflow] Run linter und spellcheck with LTS node version (#3767)
 - [workflow] Split "Run test" step into two steps for more clarity (#3767)
 
+### Fixed
+
+- [calendar] fix fullday event rrule until with timezone offset (#3781)
+ 
 ### Updated
 
 - [core] Update dependencies
 
+
path: CHANGELOG.md
user: login: sdetweil
body: from develop branch

![Screenshot at 2025-05-09 17-09-52](https://github.com/user-attachments/assets/26efe42e-e12f-40d4-b246-6f7fd6bcd152)

created_at: 2025-05-09T22:10:47Z
author_association: COLLABORATOR
diff_hunk: @@ -23,10 +23,15 @@ planned for 2025-07-01
 - [workflow] Run linter und spellcheck with LTS node version (#3767)
 - [workflow] Split "Run test" step into two steps for more clarity (#3767)
 
+### Fixed
+
+- [calendar] fix fullday event rrule until with timezone offset (#3781)
+ 
 ### Updated
 
 - [core] Update dependencies
 
+
path: CHANGELOG.md
user: login: khassel
body: `develop` has only 1 blank line, you have 2 as already stated above
created_at: 2025-05-09T22:17:03Z
author_association: COLLABORATOR

PR comments:
user: login: sdetweil
created_at: 2025-05-09T21:23:52Z
body: now.. I LITERALLY JUST pulled develop, and spent 30 minutes creating testcase etc, and NOW I have a merge conflict?? 

author_association: COLLABORATOR
user: login: sdetweil
created_at: 2025-05-09T21:27:46Z
body: I JUST ran prettier on repo

npm run test:prettier

got no error
```
(base) sam@sams:~/MagicMirror$ npm run test:prettier --write

> magicmirror@2.32.0-develop test:prettier
> prettier . --check

Checking formatting...
All matched files use Prettier code style!
```
author_association: COLLABORATOR
user: login: sdetweil
created_at: 2025-05-09T22:21:20Z
body: pulled, ran prettier --write, and its ok, so no idea what the problem is.. still passes --check

author_association: COLLABORATOR