Read the statement by Michael Teeuw here.
MMM-CalendarExt3
-
@Ragged4310
Sure, that’s doable. There’s no reason why it shouldn’t be possible.This kind of work is
domain analysis, which is basically my real-world job.However, it’s not exactly simple. By simple, I mean it’ll take more than the usual 10 minutes I’d spend diving into this hobby project that genuinely interests me.
And honestly, my rate is quite high. :D (Just kidding.)
Let’s clarify a few things first.
- CX3 is usually useful for viewing events from multiple calendars at once, so you’re probably using other types of calendars (e.g., personal meetings, family schedules) together. For this task, let’s assume that the target “customer reservation” events are in a separate, independent calendar. For convenience, I’ll call this calendar “Reservation” from now on.
- Given the nature of calendars, I assume that there won’t be any overlapping events in the Reservation calendar. Additionally, I’ll assume there are no full-day events or events that span midnight.
With these assumptions, I’ll give it a shot sometime tomorrow when I have some free time. I can’t make any promises, so please don’t get your hopes up too high.
-
See the green events


eventPayloadin CX3 config;/* CX3 config section in `config/config.js` */ eventPayload: (payload) => { const targetCalendar = "Reservation" const divideGap = 1000 * 60 * 60 * 1 const locale = 'en-US' const timeStyle = { timeStyle: 'short' } const condition = (ev) => { return (ev.calendarName === targetCalendar && ev.fullDayEvent === false) } const target = payload.filter(condition).sort((a, b) => a.endDate - b.endDate) const result = payload.filter(e => !condition(e)) const dateStr = (dateValue) => Intl.DateTimeFormat(locale, timeStyle).format(new Date(+dateValue)) const collapse = (template, ev) => { if (!template?.["title"]) template = { ...ev, collapseCount: 0, description: '', location: '' } template.collapseCount++ template.startDate = String(Math.min(+template.startDate, +ev.startDate)) template.endDate = String(Math.max(+template.endDate, +ev.endDate)) template.title = `${dateStr(template.startDate)} - ${dateStr(template.endDate)}` if (template.collapseCount > 1) template.title += ` <span class="count">${template.collapseCount}</span>` template.description += `<div class="collapsedEvent"> <p class="title">${ev.title}</p> <p class="period">${dateStr(ev.startDate)} - ${dateStr(ev.endDate)}</p>` if (ev.description) template.description += `<p class="description">${ev.description}</p>` if (ev.location) template.description += `<p class="location">${ev.location}</p>` template.description += '</div>' return template } const dateKey = (dateValue) => new Date(+dateValue).toLocaleDateString('en-CA') let collapsedEvent = {} for (const ev of target) { const currentKey = dateKey(ev.startDate) if (!collapsedEvent?.[ 'title' ]) { collapsedEvent = collapse(collapsedEvent, ev) continue } const collapsedKey = dateKey(collapsedEvent.startDate) if (collapsedKey !== currentKey || +ev.startDate - +collapsedEvent.endDate > divideGap) { result.push(collapsedEvent) collapsedEvent = collapse({}, ev) } else { collapsedEvent = collapse(collapsedEvent, ev) } } if (collapsedEvent?.[ 'title' ]) result.push(collapsedEvent) return result }You can modify
targetCalendar,divideGap,localeandetimeStylefor your purpose.To beautify;
/* css/custom.css */ .CX3 .event.calendar_Reservation .headline .time { display: none; } #CX3_POPOVER .title .count, .CX3 .calendar_Reservation .title .count { font-weight: bold; color: gray; } #CX3_POPOVER .title .count::before, .CX3 .calendar_Reservation .title .count::before { content: "["; } #CX3_POPOVER .title .count::after, .CX3 .calendar_Reservation .title .count::after { content: "]"; } #CX3_POPOVER .description .collapsedEvent { line-height: 115%; border-bottom: 2px solid silver; p { margin-top: 0; margin-bottom: 0; } .title { font-weight: bold; } .period { text-align: right; } .description { font-style: italic; white-space: pre; padding-left: 20px; } .location { font-style: italic; text-align: right; } } -
@MMRIZE said in MMM-CalendarExt3:
See the green events


eventPayloadin CX3 config;/* CX3 config section in `config/config.js` */ eventPayload: (payload) => { const targetCalendar = "Reservation" const divideGap = 1000 * 60 * 60 * 1 const locale = 'en-US' const timeStyle = { timeStyle: 'short' } const condition = (ev) => { return (ev.calendarName === targetCalendar && ev.fullDayEvent === false) } const target = payload.filter(condition).sort((a, b) => a.endDate - b.endDate) const result = payload.filter(e => !condition(e)) const dateStr = (dateValue) => Intl.DateTimeFormat(locale, timeStyle).format(new Date(+dateValue)) const collapse = (template, ev) => { if (!template?.["title"]) template = { ...ev, collapseCount: 0, description: '', location: '' } template.collapseCount++ template.startDate = String(Math.min(+template.startDate, +ev.startDate)) template.endDate = String(Math.max(+template.endDate, +ev.endDate)) template.title = `${dateStr(template.startDate)} - ${dateStr(template.endDate)}` if (template.collapseCount > 1) template.title += ` <span class="count">${template.collapseCount}</span>` template.description += `<div class="collapsedEvent"> <p class="title">${ev.title}</p> <p class="period">${dateStr(ev.startDate)} - ${dateStr(ev.endDate)}</p>` if (ev.description) template.description += `<p class="description">${ev.description}</p>` if (ev.location) template.description += `<p class="location">${ev.location}</p>` template.description += '</div>' return template } const dateKey = (dateValue) => new Date(+dateValue).toLocaleDateString('en-CA') let collapsedEvent = {} for (const ev of target) { const currentKey = dateKey(ev.startDate) if (!collapsedEvent?.[ 'title' ]) { collapsedEvent = collapse(collapsedEvent, ev) continue } const collapsedKey = dateKey(collapsedEvent.startDate) if (collapsedKey !== currentKey || +ev.startDate - +collapsedEvent.endDate > divideGap) { result.push(collapsedEvent) collapsedEvent = collapse({}, ev) } else { collapsedEvent = collapse(collapsedEvent, ev) } } if (collapsedEvent?.[ 'title' ]) result.push(collapsedEvent) return result }You can modify
targetCalendar,divideGap,localeandetimeStylefor your purpose.To beautify;
/* css/custom.css */ .CX3 .event.calendar_Reservation .headline .time { display: none; } #CX3_POPOVER .title .count, .CX3 .calendar_Reservation .title .count { font-weight: bold; color: gray; } #CX3_POPOVER .title .count::before, .CX3 .calendar_Reservation .title .count::before { content: "["; } #CX3_POPOVER .title .count::after, .CX3 .calendar_Reservation .title .count::after { content: "]"; } #CX3_POPOVER .description .collapsedEvent { line-height: 115%; border-bottom: 2px solid silver; p { margin-top: 0; margin-bottom: 0; } .title { font-weight: bold; } .period { text-align: right; } .description { font-style: italic; white-space: pre; padding-left: 20px; } .location { font-style: italic; text-align: right; } }I had to play around with it a bit to get it just right, but you nailed it!

My calendar is a lot less cluttered now. Thank you for all your help!
-
thanks for adding maxEventLines: { row: line, row: line, … } .
But I am having trouble getting it to work.
{ module: "MMM-CalendarExt3", position: "top_right", title: "", instanceId: '1', config: { mode: 'month', carouselId: "basicCalendar", locale: 'en-ZA', header: "", //headerTitleOptions: {month: 'long' , year:'numeric'}, eventTimeOptions: {timeStyle: 'short'}, useWeather: false, //maxEventLines: 1, firstDayOfWeek: 1, displayEndTime: false, useMarquee: true, displayCW: false, showMore: true, showEnd: false, weekIndex: 0, // From which week the view starts; -1 : last week, 0: this week 2: 2 weeks later, ... //weeksInView: 5, // How many weeks will be shown from `weekIndex` fontSize: "18px", eventHight: "20px", calendarSet: ['Pieter', 'US_holiday'], maxEventLines: { 4:6, 5:5, 6:4 , 7:3}, } },I would expect this line maxEventLines: { 4:6, 5:5, 6:4 , 7:3}, to limit the number of rows based on the number of weeks so this month has 6 week rows so the entries in the cell should be limited to 4?
I even did a test case just to see if it would only make one line by doing this
maxEventLines: { 3:1, 4:1, 5:1, 6:1 , 7:1, 8:1}, and it did not work.What am I missing.
thanks for your help.
-
-
@MMRIZE thanks for all your help.
I will update later and let you know if it worked.
-
@MMRIZE the updated fixed my issue.
thanks.
-
I’m working in the same situation: using an old iPad and trying to conserve space. I used this transform you posted way back in this thread:
/* In your config */ eventTransformer: (ev) => { if (!ev.isFullday) { let t = new Date(ev.startDate) let time = (t.getMinutes() === 0) ? String(t.getHours()) : String(t.getHours() + ':' + t.getMinutes()) ev.title = `<span class="myTime">${time}</span> ${ev.title}` } return ev }That works great, but how can I get it to display the hour in 12h, not 24h?
-
@Decimeter9667 time see the
eventTimeOptions -
@sdetweil Hmm… are you sure that works in the context of the transform I showed? eventTimeOptions doesn’t seem to change it.
-
you are doing it raw. Date.getHours() returns 0-23
so YOU have to check , and substract 12 if over 12
-
@sdetweil Thanks. For anyone else looking to do this, here’s how I modified the code:
eventTransformer: (ev) => { if (!ev.isFullday) { let t = new Date(ev.startDate) const amPm = t.getHours() >= 12 ? 'p' : 'a'; let time = (t.getMinutes() === 0) ? String(t.getHours() % 12 || 12) : String( (t.getHours() % 12 || 12) + ':' + t.getMinutes()) ev.title = `<span class="myTime">${time}${amPm}</span> ${ev.title}` } return ev } -
@Decimeter9667 you could have set the hour in a local variable when you did the am/pm test
// assume am. // of course you could reverse this if most of the events are in the afternoon let amPm='a' let hour=t.getHours() if(hour >= 12) { amPm= 'p'; hour-= 12 }then not have 2 more calls to getHour()
performance things creep in all the time… (as you might be doing this for all events…)
-
Hello, I’m new to MagicMirror trying to build my first. I’m having an issue with any events showing up. When I run the attached code I get a blank calendar. However when I run the regular calendar I see all the events. Am I doing something wrong?
{ module: "MMM-MonthlyCalendar", header: "Family Calendar", position: "bottom_bar", config: { updatesEvery: 30, weeksInFuture: 5, mode: "fourWeeks", firstDayofWeek: "sunday", //maximumEntries: "10000", <--- " I saw in one of the forums that this could help but it did not." calendars: [ { url: "https://calendar.google.com/calendar/ical/******basic.ics", priority: 1, }, ] } }Here is a screenshot of the regular calendar.

This is what it looks like with MMM-CalendarExt3

-
@anonymous321 2 things
what MagicMirror version is this? latest?
-
I think its the latest. I just installed it two nights ago. How would I check the version number?
-
@anonymous321 look at the messages at startu
if using pm2 thenpm2 logs --lines=xxx
xxx is number of most recent lines, default 15or look inside the package.json file
-
Version 1.9.4
-
-
Hi,
I’ve just come across this great module, but I cannot quite get it to work.
My default calendar module shows multiple google-calendar based calendars/entries.
This works fine.
However: CX3 only shows the ‘work’ calendar and I do not know why.Can anybody point me in the right direction ?
Edit: so I got it working (somehow) but the calendar entries take forever to show up in CX3.
Why is that ?[2025-02-07 12:31:47.169] [INFO] Calendar-Fetcher: Broadcasting 12 events from https://calendar.google.com/calendar/ical... [2025-02-07 12:31:47.701] [INFO] Calendar-Fetcher: Broadcasting 27 events from https://calendar.google.com/calendar/ical... [2025-02-07 12:31:48.719] [INFO] Calendar-Fetcher: Broadcasting 70 events from https://calendar.google.com/calendar/ical... [2025-02-07 12:31:49.691] [INFO] Calendar-Fetcher: Broadcasting 42 events from https://calendar.google.com/calendar/ical... [2025-02-07 12:31:50.976] [INFO] Calendar-Fetcher: Broadcasting 36 events from https://calendar.google.com/calendar/ical... [2025-02-07 12:31:56.453] [INFO] Calendar-Fetcher: Broadcasting 93 events from https://calendar.google.com/calendar/ical...Thanks
my CX3 config:
{ module: "MMM-CalendarExt3", position: "bottom_bar", title: "", config: { mode: "week", weekIndex: "0", weeksInView: "2", instanceId: "basicCalendar", //locale: 'de-DE', maxEventLines: 8, firstDayOfWeek: 1, calendarSet: null, } },my default calendar module config:
{ module: "calendar", position: "bottom_right", header: "Calendar", config: { broadcastPastEvents: true, colored: false, coloredSymbolOnly: false, maximumEntries: 8, maxTitleLength: 50, maximumNumberOfDays: 180, timeFormat: "relative", getRelative: 60 * 24, urgency: 60, calendars: [ { url: 'https://calendar.google.com/calendar...', symbol: 'calendar', // MAINZ 05 CALENDAR name: 'mainz05', colour: 'white', }, { url: 'https://calendar.google.com/calendar...', symbol: 'calendar', // MAIN GOOGLE CALENDAR name: 'personal', colour: 'white', }, { url: 'https://calendar.google.com/calendar...', symbol: 'calendar', // FORMULA 1 CALENDAR name: 'f1', colour: 'white', }, { url: 'https://calendar.google.com/calendar...', symbol: 'calendar', // EB MAINZ name: 'eb_mainz', colour: 'white', }, { url: 'https://calendar.google.com/calendar...', symbol: 'calendar', // PRO RUNDE name: 'pro_runde', colour: 'white', }, { url: 'https://calendar.google.com/calendar...', symbol: 'calendar', // BUSINESS TRIPS name: 'work', colour: 'white', }, ], }, },
Hello! It looks like you're interested in this conversation, but you don't have an account yet.
Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.
With your input, this post could be even better 💗
Register Login