# Calendar in Homey Dashboard - Complete Guide

**URL:** <https://community.homey.app/t/calendar-in-homey-dashboard-complete-guide/145709>\
**Category:** Dashboards\
**Tags:** homey-pro\
**Created:** [November 22, 2025, 8:51am UTC](https://community.homey.app/t/calendar-in-homey-dashboard-complete-guide/145709 "2025-11-22T08:51:05Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![Martin\_Scheugenpflug](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/martin_scheugenpflug/32/80036_2.png) [@Martin\_Scheugenpflug](https://community.homey.app/u/Martin_Scheugenpflug)\
**Post date:** [November 22, 2025, 8:51am UTC](https://community.homey.app/t/calendar-in-homey-dashboard-complete-guide/145709/1 "2025-11-22T08:51:05Z")

</div>

Hi everyone! 👋

I’d like to share a solution for displaying your iCloud calendars or Google calendars directly in the Homey Dashboard - with automatic travel time calculation, departure warnings, and a modern design!

 ![image](https://us1.discourse-cdn.com/flex025/uploads/athom/original/3X/9/8/98de19f9b4ec31984c2ce8120fd5d35075d99033.jpeg)

## 🎯 Features

- ✅ Shows appointments for the next 2 days (today & tomorrow)
- ✅ Automatic travel time calculation to appointments with location
- ✅ 3-level warning system: Normal / ⚠ Leave soon / 🚨 Leave NOW!
- ✅ Auto-update every 5 minutes with funny animations 🎪🚀
- ✅ Dark/Light Mode
- ✅ Multiple calendars with colored badges

## 📋 Requirements

- **HTTP Server** App ([in Homey App Store](https://homey.app/de-de/app/net.weejewel.ftpserver/HTTPFTP-Server/))
- **Public iCloud calendar URLs**

## 🔧 Installation

### 1. Install HTTP Server App

Install the app and use port 8000 (default).

### 2. Get iCloud Calendar URLs

For each calendar:

1. Go to [iCloud.com](https://www.icloud.com) → Calendar
2. Click the share icon 📤
3. Enable “Public Calendar”
4. Copy the URL
5. Change `webcal://` to `https://`

### 3. Customize Script

**Important sections in the script:**

```javascript
// CALENDAR CONFIGURATION
const calendars = [
    {
        name: "Work",
        url: "https://p66-caldav.icloud.com/published/2/YOUR_URL_HERE",
        color: "#3b82f6",
        class: "work"
    }
    // Add more calendars...
];

// HOME COORDINATES (for travel time calculation)
const homeLat = 48.1351; // ← YOUR Latitude
const homeLon = 11.5820; // ← YOUR Longitude
// Find your coordinates on Google Maps!

```

### 4. Upload File

1. Save as `calendar.html`
2. Upload to HTTP Server App
3. URL: `http://YOUR-HOMEY-IP:8000/calendar.html`

### 5. Add to Dashboard

Add a Web Widget with the URL.

## 🎨 Customization

**Colors:**

```javascript
color: "#3b82f6" // Blue
color: "#ec4899" // Pink
color: "#10b981" // Green

```

**Time Range:**

```javascript
tomorrow.setDate(tomorrow.getDate() + 2); // 2 = today+tomorrow, 3 = 3 days

```

**Update Interval:**

```javascript
setInterval(() => loadAllCalendars(true), 5 * 60 * 1000); // 5 min

```

## 🐛 Troubleshooting

- **No appointments?** → Check calendar URLs and “public” status
- **No travel times?** → Verify home coordinates
- **Missing animation?** → Clear browser cache (Ctrl+F5)

## 📝 The Script

Complete code ready to copy:

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Calendar</title>
    <style>
        :root{--bg-primary:#0f0f0f;--bg-card:#1f1f1f;--bg-hover:#2a2a2a;--text-primary:#e5e5e5;--text-secondary:#a3a3a3;--text-tertiary:#737373;--border:#2a2a2a;--accent:#3b82f6;--warning:#f59e0b;--danger:#ef4444}@media (prefers-color-scheme:light){:root{--bg-primary:#f5f5f5;--bg-card:#ffffff;--bg-hover:#f8f9fa;--text-primary:#1a1a1a;--text-secondary:#6b7280;--text-tertiary:#9ca3af;--border:#e5e7eb;--danger:#dc2626}}*{margin:0;padding:0;box-sizing:border-box}body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','SF Pro Display',sans-serif;background:var(--bg-primary);color:var(--text-primary);line-height:1.5;padding:0 0 60px}.container{max-width:100%;margin:0;padding:0}.calendar-container{background:var(--bg-card);border-radius:0;overflow:hidden;margin:0}.loading-state{text-align:center;padding:3rem 1.5rem;color:var(--text-secondary)}.funny-loader{font-size:4rem;margin-bottom:1rem;animation:bounce .6s infinite alternate}@keyframes bounce{from{transform:translateY(0) scale(1)}to{transform:translateY(-20px) scale(1.1)}}.loading-text{font-size:1.1rem;font-weight:500;animation:fadeInOut 2s infinite}@keyframes fadeInOut{0%,100%{opacity:.5}50%{opacity:1}}.funny-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:var(--bg-primary);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:9999;opacity:0;pointer-events:none;transition:opacity .3s ease}.funny-overlay.show{opacity:1;pointer-events:all}.funny-animation{font-size:6rem;margin-bottom:1.5rem;animation:crazyRotate 1s ease-in-out infinite}@keyframes crazyRotate{0%{transform:rotate(0deg) scale(1)}25%{transform:rotate(10deg) scale(1.2)}50%{transform:rotate(-10deg) scale(.9)}75%{transform:rotate(5deg) scale(1.1)}100%{transform:rotate(0deg) scale(1)}}.funny-message{font-size:1.3rem;font-weight:600;color:var(--text-primary);margin-bottom:.5rem}.funny-submessage{font-size:.95rem;color:var(--text-secondary)}.loading-spinner{width:20px;height:20px;border:2px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:spin .8s linear infinite;margin:0 auto 1rem}@keyframes spin{to{transform:rotate(360deg)}}.day-section{border-bottom:1px solid var(--border)}.day-section:last-child{border-bottom:none}.day-header{padding:1rem 1rem .75rem;display:flex;align-items:center;gap:.5rem}.day-indicator{width:10px;height:10px;border-radius:50%;background:var(--text-tertiary)}.day-indicator.today{background:#22c55e}.day-indicator.tomorrow{background:#fb923c}.day-title{font-size:.95rem;font-weight:600;color:var(--text-primary);flex:1}.day-badge{display:inline-flex;align-items:center;padding:.2rem .6rem;border-radius:.75rem;font-size:.7rem;font-weight:600;letter-spacing:.02em}.badge-today{background:rgba(34,197,94,.2);color:#22c55e}.badge-tomorrow{background:rgba(251,146,60,.2);color:#fb923c}.badge-allday{background:rgba(59,130,246,.2);color:#60a5fa}.events-list{padding:.25rem 0 .5rem}.event{padding:.75rem 1rem;border-left:3px solid transparent;transition:all .15s ease;cursor:default}.event:hover{background:var(--bg-hover)}.event.warning{border-left-color:var(--warning);background:rgba(245,158,11,.08)}.event.danger{border-left-color:var(--danger);background:rgba(239,68,68,.1)}.event-header{display:flex;align-items:flex-start;gap:.75rem}.event-time{color:var(--text-secondary);font-size:.85rem;font-weight:500;min-width:50px;font-variant-numeric:tabular-nums}.event-time.warning{color:var(--warning);font-weight:600}.event-time.danger{color:var(--danger);font-weight:600}.event-content{flex:1;min-width:0}.event-title-row{display:flex;align-items:flex-start;gap:.5rem;margin-bottom:.35rem;flex-wrap:wrap}.event-title{font-weight:500;color:var(--text-primary);font-size:.9rem;flex:1;min-width:0;word-break:break-word}.calendar-badge{display:inline-flex;align-items:center;padding:.15rem .55rem;border-radius:.5rem;font-size:.7rem;font-weight:600;white-space:nowrap;flex-shrink:0}.calendar-badge.work{background:#3b82f6;color:white}.calendar-badge.private{background:#ec4899;color:white}.calendar-badge.family{background:#10b981;color:white}.event-location{color:var(--text-secondary);font-size:.8rem;margin-top:.35rem;display:flex;align-items:flex-start;gap:.4rem;line-height:1.4}.location-icon{margin-top:.1rem;flex-shrink:0}.location-details{flex:1;min-width:0}.location-address{color:var(--text-secondary);word-break:break-word}.travel-info{display:flex;align-items:center;gap:.5rem;margin-top:.25rem;flex-wrap:wrap}.travel-time{color:var(--text-tertiary);font-size:.75rem}.travel-time.warning{color:var(--warning);font-weight:600}.travel-time.danger{color:var(--danger);font-weight:600}.departure-time{color:var(--text-tertiary);font-size:.75rem}.departure-time.warning{color:var(--warning);font-weight:600}.departure-time.danger{color:var(--danger);font-weight:600}.no-events{text-align:center;padding:3rem 1.5rem;color:var(--text-tertiary);font-size:.9rem}.footer{position:fixed;bottom:0;left:0;right:0;text-align:center;padding:.75rem;background:var(--bg-primary);border-top:1px solid var(--border);color:var(--text-tertiary);font-size:.75rem;z-index:100}.refresh-button{position:fixed;bottom:4rem;right:1rem;background:var(--bg-card);color:var(--text-primary);border:1px solid var(--border);padding:.75rem;border-radius:50%;width:48px;height:48px;cursor:pointer;box-shadow:0 4px 12px rgba(0,0,0,.3);transition:all .2s ease;display:flex;align-items:center;justify-content:center;z-index:101;font-size:1.2rem}.refresh-button:hover{transform:scale(1.1);border-color:var(--accent)}.refresh-button:active{transform:scale(.95)}@keyframes rotate{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}.refresh-button.loading{animation:rotate 1s linear infinite}
    </style>
</head>
<body>
    <div class="funny-overlay" id="funnyOverlay">
        <div class="funny-animation" id="funnyEmoji">🏃‍♂️💨</div>
        <div class="funny-message" id="funnyMessage">Loading appointments...</div>
        <div class="funny-submessage" id="funnySubmessage">Just a moment!</div>
    </div>
    <div class="container">
        <div class="calendar-container" id="calendar-container">
            <div class="loading-state">
                <div class="funny-loader">📅</div>
                <div class="loading-text">Loading...</div>
            </div>
        </div>
    </div>
    <button class="refresh-button" id="refreshBtn" onclick="manualRefresh()">↻</button>
    <div class="footer" id="lastUpdate">Loading...</div>
    <script>
const calendars=[{name:"Work",url:"https://p66-caldav.icloud.com/published/2/YOUR_URL_HERE",color:"#3b82f6",class:"work"},{name:"Private",url:"https://p66-caldav.icloud.com/published/2/YOUR_URL_HERE",color:"#ec4899",class:"private"},{name:"Family",url:"https://p112-caldav.icloud.com/published/2/YOUR_URL_HERE",color:"#10b981",class:"family"}];const CACHE_KEY='homey_calendar_cache';let isLoading=!1;const funnyAnimations=[{emoji:'🏃‍♂️💨',message:'Loading appointments...',sub:'Quick, quick!'},{emoji:'🚀',message:'Calendar rocket launching...',sub:'T-Minus 3... 2... 1...'},{emoji:'🔮',message:'Looking into the future...',sub:'The crystal ball says...'},{emoji:'🎪',message:'Juggling appointments...',sub:"Don't drop them!"},{emoji:'🎯',message:'Hitting targets...',sub:'Bullseye!'},{emoji:'🎨',message:'Painting calendar...',sub:'A masterpiece!'},{emoji:'🎭',message:'Drama appointments loading...',sub:'Curtain up!'},{emoji:'🎲',message:'Rolling appointments...',sub:'The dice are cast!'},{emoji:'🎪🤹',message:'Appointment circus running...',sub:'Ladies and gentlemen!'},{emoji:'🧙‍♂️✨',message:'Calendar magic working...',sub:'Abracadabra!'},{emoji:'🦸‍♂️',message:'Appointment hero on duty...',sub:'To the rescue!'},{emoji:'🍕',message:'Delivering appointments...',sub:'In 30 minutes or free!'},{emoji:'☕',message:'Brewing appointments...',sub:'With extra foam!'}];function showFunnyAnimation(){const random=funnyAnimations[Math.floor(Math.random()*funnyAnimations.length)];document.getElementById('funnyEmoji').textContent=random.emoji;document.getElementById('funnyMessage').textContent=random.message;document.getElementById('funnySubmessage').textContent=random.sub;document.getElementById('funnyOverlay').classList.add('show')}function hideFunnyAnimation(){document.getElementById('funnyOverlay').classList.remove('show')}function cleanAddress(address){if(!address)return '';let cleaned=address.replace(/\\,/g,',').replace(/\\n/g,' ').replace(/\\/g,'');cleaned=cleaned.replace(/\s+/g,' ').trim();return cleaned}function extractMainLocation(address){if(!address)return '';const cleaned=cleanAddress(address);const parts=cleaned.split(',');if(parts.length>0){return parts[0].trim()}return cleaned}function extractSearchAddress(address){if(!address)return '';const cleaned=cleanAddress(address);const plzMatch=cleaned.match(/\b(\d{5})\s+([A-Za-zäöüÄÖÜß\s]+)/);if(plzMatch){return `${plzMatch[1]} ${plzMatch[2].trim()}`}const parts=cleaned.split(',').map(p=>p.trim()).filter(p=>p.length>0);if(parts.length>=2){return `${parts[0]}, ${parts[1]}`}return cleaned}function parseICalDate(dateString){const year=parseInt(dateString.substring(0,4));const month=parseInt(dateString.substring(4,6))-1;const day=parseInt(dateString.substring(6,8));if(dateString.includes('T')){const hour=parseInt(dateString.substring(9,11));const minute=parseInt(dateString.substring(11,13));const second=parseInt(dateString.substring(13,15));if(dateString.endsWith('Z')){return new Date(Date.UTC(year,month,day,hour,minute,second))}return new Date(year,month,day,hour,minute,second)}return new Date(year,month,day)}function isAllDayEvent(event){return!event.DTSTART.includes('T')}function parseICalendar(icalData){const events=[];const lines=icalData.split(/\r?\n/);let currentEvent=null;let currentField=null;for(let i=0;i<lines.length;i++){let line=lines[i].trim();if(line.startsWith(' ')&&currentField&&currentEvent){currentEvent[currentField]+=line.substring(1);continue}if(line==='BEGIN:VEVENT'){currentEvent={}}else if(line==='END:VEVENT'&&currentEvent){if(currentEvent.DTSTART){events.push(currentEvent)}currentEvent=null;currentField=null}else if(currentEvent){const colonIndex=line.indexOf(':');if(colonIndex>0){const key=line.substring(0,colonIndex).split(';')[0];const value=line.substring(colonIndex+1);currentEvent[key]=value;currentField=key}}}return events}function formatTime(date){return date.toLocaleTimeString('en-US',{hour:'2-digit',minute:'2-digit'})}function formatDayHeader(date){const options={weekday:'long',day:'numeric',month:'long'};return date.toLocaleDateString('en-US',options)}function isToday(date){const today=new Date();return date.getDate()===today.getDate()&&date.getMonth()===today.getMonth()&&date.getFullYear()===today.getFullYear()}function isTomorrow(date){const tomorrow=new Date();tomorrow.setDate(tomorrow.getDate()+1);return date.getDate()===tomorrow.getDate()&&date.getMonth()===tomorrow.getMonth()&&date.getFullYear()===tomorrow.getFullYear()}function getDayKey(date){return `${date.getFullYear()}-${String(date.getMonth()+1).padStart(2,'0')}-${String(date.getDate()).padStart(2,'0')}`}async function getTravelTime(destination){if(!destination||destination.trim()===''){return null}try{const searchAddress=extractSearchAddress(destination);console.log('Searching for:',searchAddress);const url=`https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(searchAddress)}&format=json&limit=1&countrycodes=us`;const response=await fetch(url);const data=await response.json();if(data.length>0){const destLat=parseFloat(data[0].lat);const destLon=parseFloat(data[0].lon);console.log('Found:',data[0].display_name);const homeLat=40.7128;const homeLon=-74.0060;const distance=getDistance(homeLat,homeLon,destLat,destLon);const roadDistance=distance*1.3;const timeInMinutes=Math.round((roadDistance/50)*60);console.log(`Distance: ${distance.toFixed(1)} km, Travel time: ${timeInMinutes} min`);if(timeInMinutes>3){return timeInMinutes}}}catch(error){console.error('Travel time calculation failed for:',destination,error)}return null}function getDistance(lat1,lon1,lat2,lon2){const R=6371;const dLat=(lat2-lat1)*Math.PI/180;const dLon=(lon2-lon1)*Math.PI/180;const a=Math.sin(dLat/2)*Math.sin(dLat/2)+Math.cos(lat1*Math.PI/180)*Math.cos(lat2*Math.PI/180)*Math.sin(dLon/2)*Math.sin(dLon/2);const c=2*Math.atan2(Math.sqrt(a),Math.sqrt(1-a));return R*c}function shouldLeaveBy(eventTime,travelMinutes){const departureTime=new Date(eventTime);departureTime.setMinutes(departureTime.getMinutes()-travelMinutes-5);return departureTime}function getUrgencyLevel(departureTime){const now=new Date();const minutesUntilDeparture=Math.floor((departureTime-now)/(1000*60));if(minutesUntilDeparture<=0){return 'danger'}else if(minutesUntilDeparture<=15){return 'warning'}return 'normal'}async function fetchWithProxy(url){const proxies=[`https://corsproxy.io/?${encodeURIComponent(url)}`,`https://api.codetabs.com/v1/proxy?quest=${encodeURIComponent(url)}`,url];for(const proxyUrl of proxies){try{const response=await fetch(proxyUrl,{headers:{'Accept':'text/calendar,text/plain,*/*'}});if(response.ok){const text=await response.text();if(text.includes('BEGIN:VCALENDAR')||text.includes('BEGIN:VEVENT')){return text}}}catch(error){}}throw new Error('Loading failed')}function saveToCache(index,data){try{const cache=JSON.parse(localStorage.getItem(CACHE_KEY)||'{}');cache[index]=data;localStorage.setItem(CACHE_KEY,JSON.stringify(cache))}catch(e){}}function loadFromCache(index){try{const cache=JSON.parse(localStorage.getItem(CACHE_KEY)||'{}');return cache[index]}catch(e){return null}}async function loadAllCalendars(showAnimation=!1){if(isLoading)return;isLoading=!0;if(showAnimation){showFunnyAnimation()}const container=document.getElementById('calendar-container');const allEvents=[];for(let i=0;i<calendars.length;i++){const calendar=calendars[i];try{const icalData=await fetchWithProxy(calendar.url);saveToCache(i,icalData);const events=parseICalendar(icalData);events.forEach(event=>{allEvents.push({...event,calendarName:calendar.name,calendarColor:calendar.color,calendarClass:calendar.class,startDate:parseICalDate(event.DTSTART),endDate:event.DTEND?parseICalDate(event.DTEND):null,isAllDay:isAllDayEvent(event)})})}catch(error){const cachedData=loadFromCache(i);if(cachedData){const events=parseICalendar(cachedData);events.forEach(event=>{allEvents.push({...event,calendarName:calendar.name,calendarColor:calendar.color,calendarClass:calendar.class,startDate:parseICalDate(event.DTSTART),endDate:event.DTEND?parseICalDate(event.DTEND):null,isAllDay:isAllDayEvent(event)})})}}}const now=new Date();now.setHours(0,0,0,0);const tomorrow=new Date(now);tomorrow.setDate(tomorrow.getDate()+2);const futureEvents=allEvents.filter(event=>event.startDate>=now&&event.startDate<tomorrow).sort((a,b)=>a.startDate-b.startDate);const eventsByDay={};futureEvents.forEach(event=>{const dayKey=getDayKey(event.startDate);if(!eventsByDay[dayKey]){eventsByDay[dayKey]=[]}eventsByDay[dayKey].push(event)});let html='';const days=Object.keys(eventsByDay).sort();if(days.length===0){html='<div class="no-events">No appointments today or tomorrow</div>'}else{for(const dayKey of days){const dayEvents=eventsByDay[dayKey];const date=dayEvents[0].startDate;let dayBadge='';let indicatorClass='';if(isToday(date)){dayBadge='<span class="day-badge badge-today">Today</span>';indicatorClass='today'}else if(isTomorrow(date)){dayBadge='<span class="day-badge badge-tomorrow">Tomorrow</span>';indicatorClass='tomorrow'}html+=`<div class="day-section"><div class="day-header"><div class="day-indicator ${indicatorClass}"></div><span class="day-title">${formatDayHeader(date)}</span>${dayBadge}</div><div class="events-list">`;for(const event of dayEvents){let locationHTML='';let eventClass='event';let timeClass='event-time';if(event.LOCATION&&!event.isAllDay){const mainLocation=extractMainLocation(event.LOCATION);const travelMinutes=await getTravelTime(event.LOCATION);if(travelMinutes){const departureTime=shouldLeaveBy(event.startDate,travelMinutes);const urgency=getUrgencyLevel(departureTime);if(urgency!=='normal'){eventClass+=` ${urgency}`;timeClass+=` ${urgency}`}let departureText='';if(urgency==='danger'){departureText=`🚨 Leave NOW! (by ${formatTime(departureTime)})`}else if(urgency==='warning'){departureText=`⚠️ Leave soon (${formatTime(departureTime)})`}else{departureText=`Depart ${formatTime(departureTime)}`}locationHTML=`<div class="event-location"><span class="location-icon">📍</span><div class="location-details"><div class="location-address">${mainLocation}</div><div class="travel-info"><span class="travel-time ${urgency!=='normal'?urgency:''}">${travelMinutes} min drive</span><span class="departure-time ${urgency!=='normal'?urgency:''}">${departureText}</span></div></div></div>`}else{locationHTML=`<div class="event-location"><span class="location-icon">📍</span><div class="location-details"><div class="location-address">${mainLocation}</div></div></div>`}}else if(event.LOCATION){const mainLocation=extractMainLocation(event.LOCATION);locationHTML=`<div class="event-location"><span class="location-icon">📍</span><div class="location-details"><div class="location-address">${mainLocation}</div></div></div>`}const timeDisplay=event.isAllDay?'<span class="day-badge badge-allday">All day</span>':`<div class="${timeClass}">${formatTime(event.startDate)}</div>`;html+=`<div class="${eventClass}"><div class="event-header">${event.isAllDay?'':timeDisplay}<div class="event-content"><div class="event-title-row"><span class="event-title">${event.SUMMARY||'Untitled'}</span><span class="calendar-badge ${event.calendarClass}">${event.calendarName}</span></div>${event.isAllDay?timeDisplay:''}${locationHTML}</div></div></div>`}html+='</div></div>'}}container.innerHTML=html;const updateTime=new Date().toLocaleTimeString('en-US',{hour:'2-digit',minute:'2-digit'});document.getElementById('lastUpdate').textContent=`Updated: ${updateTime}`;if(showAnimation){setTimeout(()=>{hideFunnyAnimation()},1500)}isLoading=!1}async function manualRefresh(){const btn=document.getElementById('refreshBtn');btn.classList.add('loading');await loadAllCalendars(!0);btn.classList.remove('loading')}loadAllCalendars(!0);setInterval(()=>loadAllCalendars(!0),5*60*1000);
    </script>
</body>
</html>

```

## 💡 Quick Start

1. Install HTTP Server App
2. Make iCloud calendars public & copy URLs
3. Customize script: `calendars` array + coordinates (`homeLat`, `homeLon`)
4. Save as `calendar.html` & upload
5. Add to Dashboard

Have fun! 🎉 🤖​​​​​​

---

<div class="post-metadata">

**Author:** ![DirkG](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/dirkg/32/118897_2.png) [@DirkG](https://community.homey.app/u/DirkG)\
**Post date:** [November 22, 2025, 11:03am UTC](https://community.homey.app/t/calendar-in-homey-dashboard-complete-guide/145709/2 "2025-11-22T11:03:43Z")

</div>

Great tutorial! Thx! 👍

The link to the HTTP Server App doesn’t work.

---

<div class="post-metadata">

**Author:** ![Martin\_Scheugenpflug](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/martin_scheugenpflug/32/80036_2.png) [@Martin\_Scheugenpflug](https://community.homey.app/u/Martin_Scheugenpflug)\
**Post date:** [November 22, 2025, 11:31am UTC](https://community.homey.app/t/calendar-in-homey-dashboard-complete-guide/145709/3 "2025-11-22T11:31:35Z")

</div>

The code shows all appointments of the day by default, even if they’re already over. For example, an appointment from 10:00-12:00 is still displayed at 2:00 PM.

### Solution

With this adjustment, appointments are automatically hidden as soon as their end time has passed.

### Installation

Open the `calendar.html` file and search for this line (approximately in the middle of the script):

```auto
tomorrow.setDate(tomorrow.getDate()+2);const futureEvents=allEvents.filter(event=>event.startDate>=now&&event.startDate<tomorrow)

```

**Replace it with:**

```auto
tomorrow.setDate(tomorrow.getDate()+2);const currentTime=new Date();const futureEvents=allEvents.filter(event=>{const eventEnd=event.endDate||event.startDate;return eventEnd>=currentTime&&event.startDate<tomorrow})

```

### What changes?

✅ **Appointments disappear automatically after their end time**

- Appointment 10:00-12:00 → no longer visible from 12:01

- All-day appointments remain visible all day

- The display always stays current

✅ **No manual updates needed**

- The script checks the current time with every auto-update (every 5 min)

- Expired appointments are automatically removed

---

<div class="post-metadata">

**Author:** ![Martin\_Scheugenpflug](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/martin_scheugenpflug/32/80036_2.png) [@Martin\_Scheugenpflug](https://community.homey.app/u/Martin_Scheugenpflug)\
**Post date:** [November 22, 2025, 11:31am UTC](https://community.homey.app/t/calendar-in-homey-dashboard-complete-guide/145709/4 "2025-11-22T11:31:45Z")

</div>

Fixed - Thanks

---

<div class="post-metadata">

**Author:** ![Asterix](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/asterix/32/45768_2.png) [@Asterix](https://community.homey.app/u/Asterix)\
**Post date:** [November 23, 2025, 6:51pm UTC](https://community.homey.app/t/calendar-in-homey-dashboard-complete-guide/145709/5 "2025-11-23T18:51:20Z")

</div>

> [@Martin\_Scheugenpflug](#):
>
> ### 4. Upload File
> 
> 1. Save as `calendar.html`
> 2. Upload to HTTP Server App
> 3. URL: `http://YOUR-HOMEY-IP:8000/calendar.html`

Hello Martin

Thank you for your tutorial. I don’t have knowledge of programming, nor am I strong in IT stuff, unfortunately!

I tried, but I don’t get quite smart at point 4, what that means? Do I understand that correctly, it also needs ftp? How can I do this “2. Upload to HTTP Server App”?

---

<div class="post-metadata">

**Author:** ![Martin\_Scheugenpflug](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/martin_scheugenpflug/32/80036_2.png) [@Martin\_Scheugenpflug](https://community.homey.app/u/Martin_Scheugenpflug)\
**Post date:** [November 23, 2025, 7:51pm UTC](https://community.homey.app/t/calendar-in-homey-dashboard-complete-guide/145709/6 "2025-11-23T19:51:22Z")

</div>

Hi there! No worries, I’ll explain step-by-step how to upload the file to your Homey. 😊

**Important First Step:** You need to create an FTP user before you can connect!

* * *

## Initial Setup (Required!)

1. On your Homey, go to: **More → Apps → HTTP/FTP Server → Configure App**

2. Click to **add your first FTP user** (set username + password)

3. Note your **Homey’s IP address** shown on that page

* * *

## How to Upload the Calendar File

### Using FileZilla (Recommended)

**Step 1: Install FileZilla**

- Download FileZilla Client (free) from: [https://filezilla-project.org/](https://filezilla-project.org/)

- Install it on your computer

**Step 2: Connect with FileZilla**

1. Open FileZilla

2. At the top, fill in these fields:

3. Click **“Quickconnect”**

**Step 3: Upload the File**

1. On the left side (your computer): Navigate to where you saved `calendar.html`

2. On the right side (Homey): You should see the web root folder

3. Drag and drop the `calendar.html` file from left to right

4. Done! ✅

**Step 4: Access Your Calendar**

- Open your browser and go to: `http://YOUR-HOMEY-IP:8000/calendar.html`

- Example: `http://192.168.1.50:8000/calendar.html`

* * *

## Troubleshooting

**Connection Refused?**

- ✅ Did you create an FTP user in the app settings first?

- ✅ Are you using port **2100** (not 21)?

- ✅ Is Homey and your PC on the same network?

**Can’t Login?**

- Double-check username and password in **More → Apps → HTTP/FTP Server → Configure App**

**“Cannot GET /calendar.html”?**

- The file wasn’t uploaded correctly

- Check if the file is in the correct folder

- Make sure the filename is exactly `calendar.html` (not `calendar.html.txt`)

**Calendar doesn’t display correctly?**

- Clear your browser cache (Ctrl+F5)

- Check if your calendar URLs are correct and set to “public”

* * *

## Pro Tip 💡

Once uploaded successfully, you can add the calendar URL as a **Web Widget** in your Homey Dashboard for easy access!

Hope this helps! Let me know if you have any questions. 🚀

---

<div class="post-metadata">

**Author:** ![Asterix](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/asterix/32/45768_2.png) [@Asterix](https://community.homey.app/u/Asterix)\
**Post date:** [November 24, 2025, 9:40pm UTC](https://community.homey.app/t/calendar-in-homey-dashboard-complete-guide/145709/7 "2025-11-24T21:40:26Z")

</div>

Hello

Thank you for your detailed guide, really great 👍

But I’m not putting it down 😭

It works until point 7, then I can’t get anywhere.

When I try:

> [@Martin\_Scheugenpflug](#):
>
> Step 6: Access Your Calendar  
> • Open your browser and go to: [http://YOUR-HOMEY-IP:8000/kalender.html](http://YOUR-HOMEY-IP:8000/kalender.html)  
> • Example: [http://192.168.1.50:8000/kalender.html](http://192.168.1.50:8000/kalender.html)

Is the error message: Cannot GET /calendar.html

When I try:

> [@Martin\_Scheugenpflug](#):
>
> Step 1: Open Windows Explorer  
> • Press Windows + E  
> • In the address bar at the top, type: ftp://YOUR-HOMEY-IP  
> • Example: ftp://192.168.1.50  
> • Press Enter

If the error message comes: See picture =\> this translates as the following:

This folder is inaccessible.  
Make sure the filename is correct and that you have the necessary access permissions.

Details:  
The server connection could not be established.

I don’t know where the mistake is, I’ve tried for several hours now, but without success. What could be the cause, what could I try to find the fault?

 ![Fehlermeldung ftp](https://us1.discourse-cdn.com/flex025/uploads/athom/original/3X/d/7/d75fad36c2b2c42b20cac1c1a67505a85a6b0a4f.png)

---

<div class="post-metadata">

**Author:** ![Martin\_Scheugenpflug](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/martin_scheugenpflug/32/80036_2.png) [@Martin\_Scheugenpflug](https://community.homey.app/u/Martin_Scheugenpflug)\
**Post date:** [November 29, 2025, 6:50am UTC](https://community.homey.app/t/calendar-in-homey-dashboard-complete-guide/145709/8 "2025-11-29T06:50:21Z")

</div>

Hi! Thanks for trying out my guide! 😊

**The issue:** You need to create an FTP user first before you can connect!

**Setup Steps:**

1. On your Homey, go to: **More → Apps → HTTP/FTP Server → Configure App**

2. Add your first FTP user (username + password)

3. Note your Homey’s IP address shown there

**Important:** The FTP server uses **port 2100** (not the standard port 21!)

**Connect with FileZilla (recommended):**

- Host: `YOUR-HOMEY-IP`

- Username: (what you created in step 2)

- Password: (what you created in step 2)

- Port: `2100`

- Click “Quickconnect”

**Then:**

1. Navigate to the web root folder

2. Upload your `calendar.html` file

3. Access it via `http://YOUR-HOMEY-IP:8000/calendar.html`

**Quick Check:**

- ✅ FTP user created in app settings?

- ✅ Using port **2100** (not 21)?

- ✅ Homey and PC on the same network?

That should solve your connection problem! Let me know if it works! 🚀

---

<div class="post-metadata">

**Author:** ![JeroenD](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/jeroend/32/14297_2.png) [@JeroenD](https://community.homey.app/u/JeroenD)\
**Post date:** [November 29, 2025, 10:11am UTC](https://community.homey.app/t/calendar-in-homey-dashboard-complete-guide/145709/9 "2025-11-29T10:11:40Z")

</div>

I have it working for iCloud. Does it also work with outlook? I have only an ics link found for outlook.

---

<div class="post-metadata">

**Author:** ![Martin\_Scheugenpflug](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/martin_scheugenpflug/32/80036_2.png) [@Martin\_Scheugenpflug](https://community.homey.app/u/Martin_Scheugenpflug)\
**Post date:** [November 30, 2025, 7:11am UTC](https://community.homey.app/t/calendar-in-homey-dashboard-complete-guide/145709/10 "2025-11-30T07:11:41Z")

</div>

Great that you got it working with iCloud! 🎉

**Yes, Outlook works too!** The script reads the standard ICS/iCal format, which both iCloud and Outlook use.

**How to add your Outlook calendar:**

Simply add it to the `calendars` array in the script:

javascript

```auto
const calendars = [
    {
        name: "iCloud",
        url: "https://p66-caldav.icloud.com/published/2/YOUR_ICLOUD_URL",
        color: "#3b82f6",
        class: "work"
    },
    {
        name: "Outlook",
        url: "https://outlook.live.com/owa/calendar/YOUR_OUTLOOK_ICS_LINK.ics",
        color: "#0078d4", // Outlook blue
        class: "private"
    }
];

```

**Tips for Outlook ICS links:**

- If your link starts with `webcal://`, change it to `https://`

- Make sure the calendar is shared/public in Outlook settings

- The link should end with `.ics`

It should work the same way as iCloud. Let me know if you run into any issues! 🚀

---

<div class="post-metadata">

**Author:** ![JeroenD](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/jeroend/32/14297_2.png) [@JeroenD](https://community.homey.app/u/JeroenD)\
**Post date:** [November 30, 2025, 7:14am UTC](https://community.homey.app/t/calendar-in-homey-dashboard-complete-guide/145709/11 "2025-11-30T07:14:38Z")

</div>

Thank you! I have got it working, forget to update my post. Love the calendar in the dashboard.

---

<div class="post-metadata">

**Author:** ![Martin\_Scheugenpflug](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/martin_scheugenpflug/32/80036_2.png) [@Martin\_Scheugenpflug](https://community.homey.app/u/Martin_Scheugenpflug)\
**Post date:** [November 30, 2025, 7:35am UTC](https://community.homey.app/t/calendar-in-homey-dashboard-complete-guide/145709/12 "2025-11-30T07:35:44Z")

</div>

## UPDATE: Seasonal Animations, Special Days & New Layout!

Hey everyone! 👋

I’ve made some major updates to the calendar widget and wanted to share them with you. Here’s what’s new:

### ✨ New Features

**🎄 Seasonal Animations**

- **Christmas Mode** (Dec 1-27): Falling snowflakes, snow ground with sparkles, and a Christmas tree accent

- **Easter Mode** (26 days before to 2 days after): Falling Easter eggs, spring meadow with flowers, and a bunny accent

- Animation intensity increases as you get closer to the holiday!

**💕 Special Days Support**

- **Birthdays** : Confetti animation, cake accent, special event styling

- **Wedding Anniversary** : Floating rings & champagne, golden styling

- **Relationship Anniversary** : Floating hearts, pink styling

- **Monthly Anniversaries** : Subtle heart animation on your special day each month

**🇩🇪 German Public Holidays**

- Automatic calculation of all German holidays (including Easter-based ones)

- Holidays appear automatically in your calendar with special badges

**🎨 Design Improvements**

- Cleaner, more compact event cards

- Better dark/light mode support

- Clickable footer for manual refresh (no more floating button)

- Seasonal ground effects that don’t interfere with content

**🧪 Test Mode**

- Add `?testdate=2024-12-24` to URL to preview any date

- Perfect for testing seasonal animations!

### 📸 Screenshots

The calendar now shows seasonal decorations and highlights special days:

- Christmas: Snow falling, snowy ground, 🎄 accent

- Easter: Eggs & butterflies falling, flower meadow, 🐰 accent

- Birthdays: Confetti, 🎂 accent

- Anniversaries: Hearts/rings floating, 💕/💍 accent

### ⚙ Configuration

You can customize your special dates in the `SPECIAL_DATES` object:

```auto
const SPECIAL_DATES = {
    weddingDay: { day: 1, month: 7, year: 2017 }, // Your wedding date
    partner1Birthday: { day: 15, month: 3, year: 1990 }, // Partner 1
    partner2Birthday: { day: 22, month: 8, year: 1992 }, // Partner 2
    anniversaryDay: { day: 14, month: 2 } // Monthly anniversary date
};

```

📋 The Updated Code on GitHub: [GitHub - Scheugma86/homey-pro-calendar-widget: Minimalistic calendar widget with seasonal animations, German public holidays and relationship-related special days (birthdays, wedding day, monthly anniversaries). // Minimalistisches Kalender-Widget mit saisonalen Animationen, deutschen Feiertagen und besonderen Beziehungstagen (Geburtstage, Hochzeitstag, monatliche Jahrestage).](https://github.com/Scheugma86/homey-pro-calendar-widget)

---

<div class="post-metadata">

**Author:** ![Asterix](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/asterix/32/45768_2.png) [@Asterix](https://community.homey.app/u/Asterix)\
**Post date:** [December 1, 2025, 3:14pm UTC](https://community.homey.app/t/calendar-in-homey-dashboard-complete-guide/145709/13 "2025-12-01T15:14:08Z")

</div>

I created an FTP user in the HTTP/FTP app:

 ![grafik](https://us1.discourse-cdn.com/flex025/uploads/athom/original/3X/2/f/2f1c89bcb5c4bb9d84422e11c96f5a4b1e6e93c1.png)

The upload via Filezilla also seems to work:

 ![grafik](https://us1.discourse-cdn.com/flex025/uploads/athom/original/3X/3/9/39c7f5c227dcc8199c81754c32fbec188cddddea.png)

> [@Martin\_Scheugenpflug](#):
>
> Access it via `http://YOUR-HOMEY-IP:8000/calendar.html`

I assume that with this step, you mean that I have to enter this in the web browser, right? =\> Of course with my Homey IP address and port 8000

If I do this, the following error message will be sent:

 ![grafik](https://us1.discourse-cdn.com/flex025/uploads/athom/original/3X/d/d/ddd9ed18b365070ce4c2bcbe037e68230d32285a.png)

What am I doing wrong??

I would love to use the calendar on my dashboard 🙏

---

<div class="post-metadata">

**Author:** ![Savell\_Martin](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/savell_martin/32/148160_2.png) [@Savell\_Martin](https://community.homey.app/u/Savell_Martin)\
**Post date:** [December 19, 2025, 5:36pm UTC](https://community.homey.app/t/calendar-in-homey-dashboard-complete-guide/145709/14 "2025-12-19T17:36:29Z")

</div>

I’ve got this to work and its magic!

One question, how can I display multiple days like your first image?  
Mine is only showing the day were on, which is nice but would like tomorrow too…

---

<div class="post-metadata">

**Author:** ![Bert\_Bertels](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/bert_bertels/32/120862_2.png) [@Bert\_Bertels](https://community.homey.app/u/Bert_Bertels)\
**Post date:** [December 25, 2025, 7:31am UTC](https://community.homey.app/t/calendar-in-homey-dashboard-complete-guide/145709/15 "2025-12-25T07:31:48Z")

</div>

waar kan je dit script aanpassen?

---

<div class="post-metadata">

**Author:** ![JeroenD](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/jeroend/32/14297_2.png) [@JeroenD](https://community.homey.app/u/JeroenD)\
**Post date:** [December 26, 2025, 8:06pm UTC](https://community.homey.app/t/calendar-in-homey-dashboard-complete-guide/145709/16 "2025-12-26T20:06:23Z")

</div>

I have ask ChatGPT to do that for me.

---

<div class="post-metadata">

**Author:** ![RayMan69](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/rayman69/32/121273_2.png) [@RayMan69](https://community.homey.app/u/RayMan69)\
**Post date:** [December 28, 2025, 10:38am UTC](https://community.homey.app/t/calendar-in-homey-dashboard-complete-guide/145709/17 "2025-12-28T10:38:42Z")

</div>

Same problem here, anybody have the answer?

---

<div class="post-metadata">

**Author:** ![Jan\_Scheld](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/jan_scheld/32/29921_2.png) [@Jan\_Scheld](https://community.homey.app/u/Jan_Scheld)\
**Post date:** [December 28, 2025, 3:21pm UTC](https://community.homey.app/t/calendar-in-homey-dashboard-complete-guide/145709/18 "2025-12-28T15:21:27Z")

</div>

I had the same issue that only today was shown. I’ve discovered a second occurence of “tomorrow.setDate(tomorrow.getDate()+2);”. I’ve changed it here as well and it’s working like a charm, now.  
Just an idea - make the language configurable (e.g. “en-us” occurences or the texts displayed.). But it also worked out going through the script!

Great job! I was looking for an Outlook/Homey integration for quite a while!!! Awesome!

---

<div class="post-metadata">

**Author:** ![Goldunexist](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/goldunexist/32/78869_2.png) [@Goldunexist](https://community.homey.app/u/Goldunexist)\
**Post date:** [December 28, 2025, 10:11pm UTC](https://community.homey.app/t/calendar-in-homey-dashboard-complete-guide/145709/19 "2025-12-28T22:11:44Z")

</div>

@Martin_Scheugenpflug

Looks like something is not working correctly with occurrence. I have an appointment tomorrow which is a weekly occurrence and it is not showing up.

---

<div class="post-metadata">

**Author:** ![Christer\_Havarstein](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/christer_havarstein/32/116879_2.png) [@Christer\_Havarstein](https://community.homey.app/u/Christer_Havarstein)\
**Post date:** [January 2, 2026, 8:49am UTC](https://community.homey.app/t/calendar-in-homey-dashboard-complete-guide/145709/20 "2026-01-02T08:49:32Z")

</div>

Perfect @Martin_Scheugenpflug🙂

Is it possible to show appointments for the next X days? For instance 7 days.

[Next page](https://community.homey.app/t/calendar-in-homey-dashboard-complete-guide/145709.md?page=2)
