Skip to content
Draft
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
48 changes: 43 additions & 5 deletions defaultmodules/calendar/calendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -387,21 +387,38 @@ Module.register("calendar", {
if (this.config.flipDateHeaderTitle) eventWrapper.appendChild(titleWrapper);

if (event.fullDayEvent) {
titleWrapper.colSpan = "2";
titleWrapper.classList.add("align-left");
if (this.config.showEnd && !this.config.showEndsOnlyWithDuration) {
const endMomentAdjusted = eventEndDateMoment.clone().subtract(1, "second");
if (!eventStartDateMoment.isSame(endMomentAdjusted, "d")) {
const timeWrapper = document.createElement("td");
timeWrapper.className = `time light ${this.config.flipDateHeaderTitle ? "align-right " : "align-left "}${this.timeClassForUrl(event.url)}`;
timeWrapper.style.paddingLeft = "2px";
timeWrapper.style.textAlign = this.config.flipDateHeaderTitle ? "right" : "left";
timeWrapper.innerHTML = `-${CalendarUtils.capFirst(endMomentAdjusted.format(this.config.fullDayEventDateFormat))}`;
eventWrapper.appendChild(timeWrapper);
if (!this.config.flipDateHeaderTitle) titleWrapper.classList.add("align-right");
} else {
titleWrapper.colSpan = "2";
titleWrapper.classList.add("align-left");
}
} else {
titleWrapper.colSpan = "2";
titleWrapper.classList.add("align-left");
}
} else {
const timeWrapper = document.createElement("td");
timeWrapper.className = `time light ${this.config.flipDateHeaderTitle ? "align-right " : "align-left "}${this.timeClassForUrl(event.url)}`;
timeWrapper.style.paddingLeft = "2px";
timeWrapper.style.textAlign = this.config.flipDateHeaderTitle ? "right" : "left";
timeWrapper.innerHTML = eventStartDateMoment.format("LT");

// Add endDate to dataheaders if showEnd is enabled
// Add endDate to dateheaders if showEnd is enabled
if (this.config.showEnd) {
if (this.config.showEndsOnlyWithDuration && event.startDate === event.endDate) {
// no duration here, don't display end
} else {
timeWrapper.innerHTML += ` - ${CalendarUtils.capFirst(eventEndDateMoment.format("LT"))}`;
const endFormat = eventStartDateMoment.isSame(eventEndDateMoment, "d") ? "LT" : this.config.dateEndFormat;
timeWrapper.innerHTML += `-${CalendarUtils.capFirst(eventEndDateMoment.format(endFormat))}`;
}
}

Expand All @@ -423,8 +440,9 @@ Module.register("calendar", {
if (this.config.showEnd) {
// and has a duration
if (event.startDate !== event.endDate) {
const endFormat = eventStartDateMoment.isSame(eventEndDateMoment, "d") ? "LT" : this.config.dateEndFormat;
timeWrapper.innerHTML += "-";
timeWrapper.innerHTML += CalendarUtils.capFirst(eventEndDateMoment.format(this.config.dateEndFormat));
timeWrapper.innerHTML += CalendarUtils.capFirst(eventEndDateMoment.format(endFormat));
}
}

Expand Down Expand Up @@ -454,17 +472,26 @@ Module.register("calendar", {
}
if (event.fullDayEvent && this.config.nextDaysRelative) {
// Full days events within the next two days
let relativeLabel = false;
if (event.today) {
timeWrapper.innerHTML = CalendarUtils.capFirst(this.translate("TODAY"));
relativeLabel = true;
} else if (event.yesterday) {
timeWrapper.innerHTML = CalendarUtils.capFirst(this.translate("YESTERDAY"));
relativeLabel = true;
} else if (event.tomorrow) {
timeWrapper.innerHTML = CalendarUtils.capFirst(this.translate("TOMORROW"));
relativeLabel = true;
} else if (event.dayAfterTomorrow) {
if (this.translate("DAYAFTERTOMORROW") !== "DAYAFTERTOMORROW") {
timeWrapper.innerHTML = CalendarUtils.capFirst(this.translate("DAYAFTERTOMORROW"));
relativeLabel = true;
}
}
// Append end date only if a relative label replaced the start date
if (relativeLabel && this.config.showEnd && !this.config.showEndsOnlyWithDuration && !eventStartDateMoment.isSame(eventEndDateMoment, "d")) {
timeWrapper.innerHTML += `-${CalendarUtils.capFirst(eventEndDateMoment.format(this.config.fullDayEventDateFormat))}`;
}
}
} else {
// Show relative times
Expand Down Expand Up @@ -501,11 +528,22 @@ Module.register("calendar", {
timeWrapper.innerHTML = CalendarUtils.capFirst(this.translate("DAYAFTERTOMORROW"));
}
}
// Show end date for multi-day full-day events if showEnd is configured
if (this.config.showEnd && !this.config.showEndsOnlyWithDuration) {
const endMomentAdjusted = eventEndDateMoment.clone().subtract(1, "second");
if (!eventStartDateMoment.isSame(endMomentAdjusted, "d")) {
timeWrapper.innerHTML += `-${CalendarUtils.capFirst(endMomentAdjusted.format(this.config.fullDayEventDateFormat))}`;
}
}
Log.info("[calendar] event fullday");
} else if (eventStartDateMoment.diff(now, "h") < this.config.getRelative) {
Log.info("[calendar] not full day but within getRelative size");
// If event is within getRelative hours, display 'in xxx' time format or moment.fromNow()
timeWrapper.innerHTML = `${CalendarUtils.capFirst(eventStartDateMoment.fromNow())}`;
} else if (this.config.showEnd && (!this.config.showEndsOnlyWithDuration || event.startDate !== event.endDate)) {
Copy link

@kkangshawn kkangshawn Mar 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @KristjanESPERANTO for the quick response! And additional functionality for dateEndFormat as well.
This works perfectly with the corner case which is the multiple day with specific time values.

Luckily or not, I found this line affects the existing behavior. The single day event is showing the end date even though the start date and the end date are same.

Image

I am not sure if this is expected behavior but this line seems like it is supposed to show the end date when the startDate and endDate are different. From my debugging, the event.startDate !== event.endDate returns always true even if the dates are same. (Please see the reds above)

For draft, I checked below code fits this case. I'd appreciate it if you check this once again.

} else if (this.config.showEnd && (!this.config.showEndsOnlyWithDuration && !eventStartDateMoment.isSame(eventEndDateMoment, "d"))) {

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your point. I need to think this through more deeply.

// Show end time for timed events
const endFormat = eventStartDateMoment.isSame(eventEndDateMoment, "d") ? "LT" : this.config.dateEndFormat;
timeWrapper.innerHTML += `-${CalendarUtils.capFirst(eventEndDateMoment.format(endFormat))}`;
}
} else {
// Ongoing event
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const config = {
address: "0.0.0.0",
ipWhitelist: [],

timeFormat: 24,
modules: [
{
module: "calendar",
position: "bottom_bar",
config: {
fade: false,
urgency: 0,
dateFormat: "Do.MMM, HH:mm",
dateEndFormat: "Do.MMM, HH:mm",
fullDayEventDateFormat: "Do.MMM",
timeFormat: "dateheaders",
getRelative: 0,
showEnd: true,
calendars: [
{
maximumEntries: 100,
url: "http://localhost:8080/tests/mocks/event_with_time_over_multiple_days_yearly.ics"
}
]
}
}
]
};

/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const config = {
address: "0.0.0.0",
ipWhitelist: [],

timeFormat: 24,
modules: [
{
module: "calendar",
position: "bottom_bar",
config: {
fade: false,
urgency: 0,
dateFormat: "Do.MMM, HH:mm",
dateEndFormat: "Do.MMM, HH:mm",
fullDayEventDateFormat: "Do.MMM",
timeFormat: "relative",
getRelative: 0,
showEnd: true,
calendars: [
{
maximumEntries: 100,
url: "http://localhost:8080/tests/mocks/event_with_time_over_multiple_days_yearly.ics"
}
]
}
}
]
};

/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
let config = {
address: "0.0.0.0",
ipWhitelist: [],

timeFormat: 24,
modules: [
{
module: "calendar",
position: "bottom_bar",
config: {
fade: false,
urgency: 0,
fullDayEventDateFormat: "Do.MMM",
timeFormat: "dateheaders",
getRelative: 0,
showEnd: true,
calendars: [
{
maximumEntries: 100,
url: "http://localhost:8080/tests/mocks/fullday_event_over_multiple_days_nonrepeating.ics"
}
]
}
}
]
};

/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
let config = {
address: "0.0.0.0",
ipWhitelist: [],

timeFormat: 24,
modules: [
{
module: "calendar",
position: "bottom_bar",
config: {
fade: false,
urgency: 0,
fullDayEventDateFormat: "Do.MMM",
timeFormat: "absolute",
getRelative: 0,
showEnd: true,
nextDaysRelative: true,
calendars: [
{
maximumEntries: 100,
url: "http://localhost:8080/tests/mocks/fullday_event_over_multiple_days_nonrepeating.ics"
}
]
}
}
]
};

/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
let config = {
address: "0.0.0.0",
ipWhitelist: [],

timeFormat: 24,
modules: [
{
module: "calendar",
position: "bottom_bar",
config: {
fade: false,
urgency: 0,
fullDayEventDateFormat: "Do.MMM",
timeFormat: "relative",
getRelative: 0,
showEnd: true,
calendars: [
{
maximumEntries: 100,
url: "http://localhost:8080/tests/mocks/fullday_event_over_multiple_days_nonrepeating.ics"
}
]
}
}
]
};

/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
29 changes: 29 additions & 0 deletions tests/electron/modules/calendar_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,18 @@ describe("Calendar module", () => {
});
});

describe("showEnd for timed multi-day events", () => {
it("relative timeFormat shows start and end for timed multi-day events", async () => {
await helpers.startApplication("tests/configs/modules/calendar/event_with_time_over_multiple_days_non_repeating_display_end_relative.js", "08 Oct 2024 12:30:00 GMT-07:00", [], "America/Chicago");
await expect(doTestTableContent(".calendar .event", ".time", "25th.Oct, 20:00-26th.Oct, 06:00", first)).resolves.toBe(true);
});

it("dateheaders timeFormat shows end for timed multi-day events", async () => {
await helpers.startApplication("tests/configs/modules/calendar/event_with_time_over_multiple_days_non_repeating_display_end_dateheaders.js", "08 Oct 2024 12:30:00 GMT-07:00", [], "America/Chicago");
await expect(doTestTableContent(".calendar .event", ".time", "-26th.Oct, 06:00", first)).resolves.toBe(true);
});
});

describe("count and check symbols", () => {
it("in array", async () => {
await helpers.startApplication("tests/configs/modules/calendar/symboltest.js", "08 Oct 2024 12:30:00 GMT-07:00", [], "America/Chicago");
Expand All @@ -314,4 +326,21 @@ describe("Calendar module", () => {
await expect(doTestTableContent(".testNotification", ".elementCount", "12", first)).resolves.toBe(true);
});
});

describe("showEnd for multi-day full-day events", () => {
it("relative timeFormat shows start and end date", async () => {
await helpers.startApplication("tests/configs/modules/calendar/fullday_multiday_showend_relative.js", "08 Oct 2024 12:30:00 GMT-07:00", [], "America/Chicago");
await expect(doTestTableContent(".calendar .event", ".time", "25th.Oct-30th.Oct", first)).resolves.toBe(true);
});

it("dateheaders timeFormat shows end date in time cell", async () => {
await helpers.startApplication("tests/configs/modules/calendar/fullday_multiday_showend_dateheaders.js", "08 Oct 2024 12:30:00 GMT-07:00", [], "America/Chicago");
await expect(doTestTableContent(".calendar .event", ".time", "-30th.Oct", first)).resolves.toBe(true);
});

it("absolute timeFormat with nextDaysRelative shows relative label and end date", async () => {
await helpers.startApplication("tests/configs/modules/calendar/fullday_multiday_showend_nextdaysrelative.js", "24 Oct 2024 12:30:00 GMT-07:00", [], "America/Chicago");
await expect(doTestTableContent(".calendar .event", ".time", "Tomorrow-30th.Oct", first)).resolves.toBe(true);
});
});
});
18 changes: 18 additions & 0 deletions tests/mocks/event_with_time_over_multiple_days_yearly.ics
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//MagicMirror Test//timed-multiday-yearly//EN
CALSCALE:GREGORIAN
BEGIN:VEVENT
DTSTART:20241026T010000Z
DTEND:20241026T110000Z
DTSTAMP:20241024T153358Z
UID:4maud6s79m41a99pj2g7j5km0a@google.com
CREATED:20241024T153313Z
LAST-MODIFIED:20241024T153330Z
SEQUENCE:0
STATUS:CONFIRMED
RRULE:FREQ=YEARLY
SUMMARY:Sleep over at Bobs
TRANSP:OPAQUE
END:VEVENT
END:VCALENDAR