Weiter zum Inhalt
  • Home
  • Aktuell
  • Tags
  • 0 Ungelesen 0
  • Kategorien
  • Unreplied
  • Beliebt
  • GitHub
  • Docu
  • Hilfe
Skins
  • Hell
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dunkel
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Standard: (Kein Skin)
  • Kein Skin
Einklappen
ioBroker Logo

Community Forum

donate donate
  1. ioBroker Community Home
  2. Deutsch
  3. Visualisierung
  4. Google Kalender (ICS) für Material Design & Mini-Kalender

NEWS

  • Der neue Monatsrückblick für Mai und Juni 2026 ist online!
    BluefoxB
    Bluefox
    7
    1
    212

  • wichtiges UPDATE für controller 7.2.2 im stable
    HomoranH
    Homoran
    10
    1
    3.3k

  • Neues YouTube-Video: Visualisierung im Devices-Adapter
    BluefoxB
    Bluefox
    16
    1
    5.1k

Google Kalender (ICS) für Material Design & Mini-Kalender

Geplant Angeheftet Gesperrt Verschoben Visualisierung
vis
34 Beiträge 5 Kommentatoren 182 Aufrufe 6 Beobachtet
  • Älteste zuerst
  • Neuste zuerst
  • Meiste Stimmen
Antworten
  • In einem neuen Thema antworten
Anmelden zum Antworten
Dieses Thema wurde gelöscht. Nur Nutzer mit entsprechenden Rechten können es sehen.
  • Ro75R Ro75

    Einleitung

    Für ioBroker gibt es bereits einen Adapter zum Einbinden von Google-Kalendern. Da dieser jedoch einige Funktionen vermissen ließ, die ich für meine Visualisierung benötigte, habe ich ein eigenes Script erstellt. Dieses ist speziell auf die Darstellung in Material Design und meine Anforderungen zugeschnitten.

    Das Script liest beliebig viele Google-Kalender über deren private ICS-Adresse ein, verarbeitet wiederkehrende Termine, ergänzt automatisch Feiertage (Deutschland oder Österreich) sowie Sommer- und Winterzeit und erzeugt zwei Datenpunkte für unterschiedliche Darstellungen.

    Unbenannt_anonymized.png


    Voraussetzungen

    • JavaScript-Adapter
    • Installiertes npm-Modul:
      • node-ical (als zusätzliches NPM-Paket - aktuelle Version: 0.26.1)
    • Mindestens ein Google-Kalender mit privater ICS-Adresse
    • Material Design (Scrounger 0.5.94) (VIS-1) für großen Kalender
    • JSON oder Tabellen Widget für kleinen Kalender
      @oliverio hier könntest du eventuell über vis-jsontemplate noch etwas beitragen

    Konfiguration

    Alle Einstellungen befinden sich übersichtlich am Anfang des Scripts und können ohne Änderungen am eigentlichen Code angepasst werden.

    Konstante Beschreibung Mögliche Werte
    dp_Main Datenpunktpfad für die erzeugten Datenpunkte Beliebiger Datenpunktpfad
    BASE_URL Pfad zu den Icons des Mini-Kalenders URL oder lokaler VIS-Pfad
    C1_PAST_DAYS Vergangene Tage im großen Kalender Zahl ≥ 0
    C1_FUTURE_DAYS Zukünftige Tage im großen Kalender Zahl ≥ 0
    M1_PAST_DAYS Vergangene Tage im Mini-Kalender Zahl ≥ 0
    M1_FUTURE_DAYS Zukünftige Tage im Mini-Kalender Zahl ≥ 0
    LAND Land für die Feiertagsberechnung DE oder AT
    Bundesland Bundesland für regionale Feiertage DE: BW, BY, BE, BB, HB, HH, HE, MV, NI, NW, RP, SL, SN, ST, SH, TH<br>AT: W, NÖ, OÖ, S, T, V, St, K, B
    CALENDARS Eingebundene Google-Kalender Besitzer, ICS-Adresse, Farbe und Icon
    KEYWORD_STYLE Individuelle Farben und Emojis Frei erweiterbar

    Kalender konfigurieren

    Jeder Google-Kalender wird in der Konstante CALENDARS eingetragen.

    Beispiel:

    {
        owner: "Mama",
        url: "https://....basic.ics",
        color: "#a71b1b",
        img: BASE_URL + "Mama.png"
    }
    

    Bedeutung der einzelnen Einträge

    Parameter Beschreibung
    owner Frei wählbare Bezeichnung des Kalenders
    url Private ICS-Adresse des Google-Kalenders
    color Standardfarbe für die Darstellung im großen Kalender
    img Icon für die Darstellung im Mini-Kalender

    Es können beliebig viele Kalender eingebunden werden.

    Hinweis

    Die Bezeichnung aus owner wird gleichzeitig als Filter für den Mini-Kalender verwendet.

    Dadurch können später gezielt Termine einzelner Kalender angezeigt werden, z. B.:

    • ALLE
    • Mama
    • Papa
    • Kind
    • Arbeit
    • Verein

    Die möglichen Filter entsprechen immer den vergebenen owner-Namen.

    Unbenannt_anonymized2.png


    Individuelle Schlüsselwörter

    Über KEYWORD_STYLE können beliebige Schlüsselwörter definiert werden.

    Wird eines dieser Wörter im Titel eines Kalendereintrags gefunden, wird automatisch:

    • eine eigene Farbe verwendet
    • optional ein Emoji angehängt

    Beispiele:

    Schlüsselwort Ergebnis
    geburt 🎂 und definierte Geburtstagsfarbe
    kiko 🐱 und definierte Farbe
    loki 🐱 und definierte Farbe

    Natürlich können beliebig viele eigene Begriffe ergänzt werden.

    Die Erkennung erfolgt unabhängig von Groß- und Kleinschreibung.


    Besondere Funktionen

    Geburtstage

    Geburtstage werden automatisch erkannt und entsprechend hervorgehoben.

    Hierfür muss der Titel des Kalendereintrags lediglich das Wort „Geburtstag“ enthalten.

    Beispiele:

    • Geburtstag Oma
    • Geburtstag Max
    • Geburtstag Lisa Müller

    Das Script erkennt den Begriff automatisch und versieht den Termin mit dem 🎂-Emoji sowie der in KEYWORD_STYLE definierten Farbe.


    "In Memory"-Einträge

    Auch Gedenktage oder Jahrestage verstorbener Personen können automatisch erkannt werden.

    Hierfür muss der Titel des Kalendereintrags den Text „In Memory“ enthalten.

    Beispiele:

    • In Memory Opa
    • In Memory Oma
    • In Memory Max Mustermann

    Diese Termine werden im großen Kalender automatisch:

    • grau dargestellt
    • mit einem 🕯️-Emoji versehen
    • bewusst dezent hervorgehoben

    Im Mini-Kalender werden diese Einträge automatisch ausgeblendet, damit die Terminliste übersichtlich bleibt.

    Die Erkennung erfolgt ebenfalls unabhängig von Groß- und Kleinschreibung.


    Unterstützte Funktionen

    Das Script bietet unter anderem:

    • Einbindung beliebig vieler Google-Kalender
    • Unterstützung wiederkehrender Termine
    • Verarbeitung von Ausnahmen (EXDATE)
    • Automatische Erkennung von Geburtstagen
    • Automatische Erkennung von "In Memory"-Einträgen
    • Gesetzliche Feiertage für Deutschland
    • Gesetzliche Feiertage für Österreich
    • Regionale Feiertage je Bundesland
    • Automatische Anzeige von Sommer- und Winterzeit
    • Individuelle Farben pro Kalender
    • Individuelle Icons pro Kalender
    • Keyword-Erkennung mit eigener Farbzuweisung
    • Emoji-Unterstützung
    • Transparente Darstellung bereits vergangener Termine
    • Besitzerfilter für den Mini-Kalender
    • Automatische Aktualisierung alle 5 Minuten

    Datenpunkte

    Das Script erstellt automatisch folgende Datenpunkte:

    Datenpunkt Beschreibung
    0_userdata.0.calendar.c1 Daten für den großen Kalender (Material Design / Scrounger)
    0_userdata.0.calendar.m1 Daten für den Mini-Kalender
    0_userdata.0.calendar.inhaber Filter für den Mini-Kalender

    Über den Datenpunkt 0_userdata.0.calendar.inhaber kann jederzeit festgelegt werden, welche Termine im Mini-Kalender angezeigt werden.

    Beispiele:

    • ALLE
    • Mama
    • Papa
    • Kind
    • Arbeit
    • Verein

    Die möglichen Werte entsprechen den unter owner vergebenen Namen.

    Unbenannt_anonymized3.png


    Feiertage

    Das Script ergänzt automatisch gesetzliche Feiertage.

    Unterstützt werden:

    • Deutschland
    • Österreich

    Abhängig vom eingestellten Bundesland werden automatisch nur die gültigen regionalen Feiertage angezeigt.

    Zusätzlich werden automatisch folgende Termine erzeugt:

    • Beginn der Sommerzeit ☀️
    • Beginn der Winterzeit ❄️

    Anpassungsmöglichkeiten

    Folgende Bereiche können jederzeit angepasst werden:

    • Kalender hinzufügen oder entfernen
    • Farben ändern
    • Icons austauschen
    • Anzeigezeiträume ändern
    • Schlüsselwörter erweitern
    • Land wechseln (DE / AT)
    • Bundesland ändern

    Hinweise

    Für den Zugriff auf einen Google-Kalender wird dessen private ICS-Adresse benötigt.

    Diese findet ihr in den Einstellungen des jeweiligen Kalenders unter „Geheime Adresse im iCal-Format“.

    Die erzeugten Datenpunkte können anschließend direkt in Material Design (Scrounger) oder jeder anderen Visualisierung verwendet werden.


    Feedback

    Das Script ist aus meinen eigenen Anforderungen entstanden und wird bei Bedarf weiterentwickelt.

    Ich freue mich über Verbesserungsvorschläge, Ideen oder Fehlerberichte. Vielleicht ist das Script auch für andere eine interessante Alternative oder Ergänzung zum vorhandenen Google-Kalender-Adapter.

    Das Script (Code)

    // === Google Kalender (ICS) für Material Design & Mini-Kalender ===
     
    //Erst-Version 1.0.0 - 18.07.2026
    //Ersteller Ro75.
     
    //Voraussetzungen (Version 1.0.0 getestet mit)
    //NodeJS: 22.22.x
    //Javascript-Adapter: 9.0.18
    //Admin-Adapter: 7.8.23
    //js-controler: 7.2.2
    
    //********** START KONFIGURATION **********
    
    // @ts-nocheck
    const dp_Main    = "0_userdata.0.calendar."; //Pfad für Datenpunkt
    
    const BASE_URL   = 'http://192.168.10.99:8082/vis.0/'; //Bildpfad für Kalenderbilder z.B. Familienbilder (Mama, Papa, Kind)
    
    const C1_PAST_DAYS   = 30;  // Anzahl Tage rückwärts für großen Kalender (materialdesign - Scrounger) (Vergangenheit)
    const C1_FUTURE_DAYS = 120; // Anzahl Tage vorwärts für großen Kalender (materialdesign - Scrounger) (Zukunft)
    
    const M1_PAST_DAYS   = 0;   // Anzahl Tage rückwärts für Minikalender (Vergangenheit)
    const M1_FUTURE_DAYS = 30;  // Anzahl Tage vorwärts für Minikalender (Zukunft)
    
    // Deutschland (DE), Österreich (AT)
    const LAND = "DE";          //dein Land
    
    // Deutschland: Süden/Westen: BW (Baden-Württemberg), BY (Bayern), HE (Hessen), RP (Rheinland-Pfalz), SL (Saarland)
    // Deutschland: Norden/Osten: BE (Berlin), BB (Brandenburg), HB (Bremen), HH (Hamburg), MV (Mecklenburg-Vorpommern), NI (Niedersachsen), NW (Nordrhein-Westfalen), SN (Sachsen), ST (Sachsen-Anhalt), SH (Schleswig-Holstein), TH (Thüringen)
    // Österreichische Landespatrone: Josef (T,V), Florian (OÖ), Rupert (S), Leopold (W,NÖ), Martin (B), Klemens (K)
    const Bundesland = "SN";    // dein Bundesland
    
    // Liste aller eingebundenen Google-Kalender (jeweils private ICS-Adresse)
    const CALENDARS = [
        {
            owner: "Mama", // Besitzer/Zuordnung - Nach dieser Bezeichnung kann auch ein Filter ("0_userdata.0.calendar.inhaber") gesetzt werden
            url: "https://calendar.google.com/calendar/ical/XXXXXXXXXXXXXXXXXXXXXXgmail.com/private-faXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/basic.ics", // private ICS-URL
            color: "#a71b1b", // Standardfarbe des Kalenders - großer Kalender (materialdesign - Scrounger)
            img: BASE_URL + "Mama.png" // Icon für Darstellung - kleiner Kalender
        },
        {
            owner: "Kind",
            url: "https://calendar.google.com/calendar/ical/t3lYYYYYYYYYYYYYYYYYYYYYYYgroup.calendar.google.com/private-341YYYYYYYYYYYYYYYYYYYYYYYYYYYY/basic.ics",
            color: "#e4c441",
            img: BASE_URL + "Kind2.png"
        },
        {
            owner: "Papa",
            url: "https://calendar.google.com/calendar/ical/0suZZZZZZZZZZZZZZZZZZZZZZZZ%40group.calendar.google.com/private-9e0ZZZZZZZZZZZZZZZZZZZZZZZZ/basic.ics",
            color: "#039be5",
            img: BASE_URL + "Papa.png"
        }
    ];
    
    // Stilregeln für bestimmte Schlüsselwörter in Ereignisnamen (z.B. Farbe + Emoji)
    const KEYWORD_STYLE = {
        "geburt": { color: "#33B679", emoji: " 🎂" },   // Beispiel: Geburtstage - Ein Kalendereintrag lautet z.B. "Geburtstag Oma Helga" oder "Geburtstag Klaus"
        "kiko":   { color: "#8E24AA", emoji: " 🐱" },   // Beispiel: benutzerdefinierte Namen - Ein Kalendereintrag lautet z.B. "Tierarzt Kiko"
        "loki":   { color: "#8E24AA", emoji: " 🐱" }    // Beispiel: benutzerdefinierte Namen - Ein Kalendereintrag lautet z.B. "Tierarzt Loki"
    };
    
    
    //********************************** AB HIER KEINE ÄNDERUNGEN MEHR **********************************/
    const ical  = require('node-ical');
    const axios = require('axios');
    
    const DP_C1      = dp_Main + "c1";
    const DP_M1      = dp_Main + "m1";
    const DP_INHABER = dp_Main + "inhaber";
    
    const HOLIDAYS_BY_STATE = {
        "Neujahr": ["BW","BY","BE","BB","HB","HH","HE","MV","NI","NW","RP","SL","SN","ST","SH","TH"],
        "Karfreitag": ["BW","BY","BE","BB","HB","HH","HE","MV","NI","NW","RP","SL","SN","ST","SH","TH"],
        "Ostersonntag": ["BW","BY","BE","BB","HB","HH","HE","MV","NI","NW","RP","SL","SN","ST","SH","TH"],
        "Ostermontag": ["BW","BY","BE","BB","HB","HH","HE","MV","NI","NW","RP","SL","SN","ST","SH","TH"],
        "Tag der Arbeit": ["BW","BY","BE","BB","HB","HH","HE","MV","NI","NW","RP","SL","SN","ST","SH","TH"],
        "Christi Himmelfahrt": ["BW","BY","BE","BB","HB","HH","HE","MV","NI","NW","RP","SL","SN","ST","SH","TH"],
        "Pfingstmontag": ["BW","BY","BE","BB","HB","HH","HE","MV","NI","NW","RP","SL","SN","ST","SH","TH"],
        "Tag der Deutschen Einheit": ["BW","BY","BE","BB","HB","HH","HE","MV","NI","NW","RP","SL","SN","ST","SH","TH"],
        "1. Weihnachtsfeiertag": ["BW","BY","BE","BB","HB","HH","HE","MV","NI","NW","RP","SL","SN","ST","SH","TH"],
        "2. Weihnachtsfeiertag": ["BW","BY","BE","BB","HB","HH","HE","MV","NI","NW","RP","SL","SN","ST","SH","TH"],
    
        "Heilige Drei Könige": ["BW","BY","ST"],
        "Fronleichnam": ["BW","BY","HE","NW","RP","SL"],
        "Mariä Himmelfahrt": ["SL","BY"],
        "Reformationstag": ["BB","MV","SN","ST","TH","SH","HH","NI"],
        "Allerheiligen": ["BW","BY","NW","RP","SL"],
        "Buß- und Bettag": ["SN"]
    };
    
    const HOLIDAYS_BY_STATE_AT = {
        "Neujahr": ["W","NÖ","OÖ","S","T","V","St","K","B"],
        "Heilige Drei Könige": ["W","NÖ","OÖ","S","T","V","St","K","B"],
        "Ostersonntag": ["W","NÖ","OÖ","S","T","V","St","K","B"],
        "Ostermontag": ["W","NÖ","OÖ","S","T","V","St","K","B"],
        "Staatsfeiertag": ["W","NÖ","OÖ","S","T","V","St","K","B"],
        "Christi Himmelfahrt": ["W","NÖ","OÖ","S","T","V","St","K","B"],
        "Pfingstsonntag": ["W","NÖ","OÖ","S","T","V","St","K","B"],
        "Pfingstmontag": ["W","NÖ","OÖ","S","T","V","St","K","B"],
        "Fronleichnam": ["W","NÖ","OÖ","S","T","V","St","K","B"],
        "Mariä Himmelfahrt": ["W","NÖ","OÖ","S","T","V","St","K","B"],
        "Nationalfeiertag": ["W","NÖ","OÖ","S","T","V","St","K","B"],
        "Allerheiligen": ["W","NÖ","OÖ","S","T","V","St","K","B"],
        "Mariä Empfängnis": ["W","NÖ","OÖ","S","T","V","St","K","B"],
        "1. Weihnachtsfeiertag": ["W","NÖ","OÖ","S","T","V","St","K","B"],
        "2. Weihnachtsfeiertag": ["W","NÖ","OÖ","S","T","V","St","K","B"],
    
        "Josef": ["T","V"],
        "Florian": ["OÖ"],
        "Rupert": ["S"],
        "Leopold": ["W","NÖ"],
        "Martin": ["B"],
        "Klemens": ["K"]
    };
    
    let cachedEvents = [];
    
    async function smartCreateState(id, value, common = {}) {
        if (!existsState(id)) {
            await createState(id, value, common);
        }
    }
    
    function setStateIfChanged(id, value) {
        const state = getState(id);
        if (!state) return;
    
        const old = state.val;
        const isObject = v => v !== null && typeof v === "object";
    
        if (isObject(value)) {
            const newStr = JSON.stringify(value);
            let oldStr = null;
    
            if (typeof old === "string") oldStr = old;
            else if (isObject(old)) oldStr = JSON.stringify(old);
    
            if (newStr === oldStr) return;
            return setState(id, newStr, true);
        }
    
        if (old === value) return;
        setState(id, value, true);
    }
    
    async function initStates() {
        await smartCreateState(DP_C1, "[]", {read: true, write: false, type: "string", name: 'Kalender groß'});
        await smartCreateState(DP_M1, "[]", {read: true, write: false, type: "string", name: 'Kalender klein'});
        await smartCreateState(DP_INHABER, "ALLE", {read: true, write: true, type: "string", name: 'Inhaberfilter'});
    }
    
    function hexToRgba(hex, alpha = 1) {
        hex = hex.replace(/^#/, '');
        const bigint = parseInt(hex, 16);
        return `rgba(${(bigint >> 16) & 255}, ${(bigint >> 8) & 255}, ${bigint & 255}, ${alpha})`;
    }
    
    function fadeColor(hex, alpha = 0.25) {
        hex = hex.replace(/^#/, '');
        const bigint = parseInt(hex, 16);
        let r = (bigint >> 16) & 255;
        let g = (bigint >> 8) & 255;
        let b = bigint & 255;
    
        r = Math.round(r + (255 - r) * 0.55);
        g = Math.round(g + (255 - g) * 0.55);
        b = Math.round(b + (255 - b) * 0.55);
    
        return `rgba(${r}, ${g}, ${b}, ${alpha})`;
    }
    
    function getKeywordColorEmoji(event, fallback) {
        const e = (event || "").toLowerCase();
    
        for (const key in KEYWORD_STYLE) {
            if (e.includes(key)) return KEYWORD_STYLE[key];
        }
    
        return { color: fallback, emoji: "" };
    }
    
    function formatDate(date, format) {
        const pad = n => n.toString().padStart(2, '0');
        const yyyy = date.getFullYear();
        const mm   = pad(date.getMonth() + 1);
        const dd   = pad(date.getDate());
        const HH   = pad(date.getHours());
        const MM   = pad(date.getMinutes());
    
        const f = {
            "YYYY-MM-DD HH:mm": `${yyyy}-${mm}-${dd} ${HH}:${MM}`,
            "DD.MM.YYYY HH:mm": `${dd}.${mm}.${yyyy} ${HH}:${MM}`,
            "DD.MM.YYYY":       `${dd}.${mm}.${yyyy}`,
            "YYYY-MM-DD":       `${yyyy}-${mm}-${dd}`,
            "HH:mm":            `${HH}:${MM}`
        };
        return f[format];
    }
    
    function isInMemoryEvent(norm) {
        return norm.event.toLowerCase().includes("in memory");
    }
    
    function lastSunday(year, month) {
        const d = new Date(year, month + 1, 0);
        while (d.getDay() !== 0) d.setDate(d.getDate() - 1);
        return d;
    }
    
    function getEasterDate(year) {
        const f = Math.floor;
        const G = year % 19;
        const C = f(year / 100);
        const H = (C - f(C / 4) - f((8 * C + 13) / 25) + 19 * G + 15) % 30;
        const I = H - f(H / 28) * (1 - f(29 / (H + 1)) * f((21 - G) / 11));
        const J = (year + f(year / 4) + I + 2 - C + f(C / 4)) % 7;
        const L = I - J;
        const month = 3 + f((L + 40) / 44);
        const day   = L + 28 - 31 * f(month / 4);
        return new Date(year, month - 1, day);
    }
    
    function getHolidays(year) {
        const easter = getEasterDate(year);
        const add = (date, days) => new Date(date.getTime() + days * 86400000);
    
        if (LAND === "DE") {
            const MAP = HOLIDAYS_BY_STATE;
    
            const holidays = [
                { name: "Neujahr 🎉",                 date: new Date(year, 0, 1),  states: MAP["Neujahr"] },
                { name: "Karfreitag 🎉",              date: add(easter, -2),       states: MAP["Karfreitag"] },
                { name: "Ostersonntag 🎉",            date: easter,                states: MAP["Ostersonntag"] },
                { name: "Ostermontag 🎉",             date: add(easter, 1),        states: MAP["Ostermontag"] },
                { name: "Tag der Arbeit 🎉",          date: new Date(year, 4, 1),  states: MAP["Tag der Arbeit"] },
                { name: "Christi Himmelfahrt 🎉",     date: add(easter, 39),       states: MAP["Christi Himmelfahrt"] },
                { name: "Pfingstmontag 🎉",           date: add(easter, 50),       states: MAP["Pfingstmontag"] },
                { name: "Tag der Deutschen Einheit 🎉", date: new Date(year, 9, 3), states: MAP["Tag der Deutschen Einheit"] },
                { name: "1. Weihnachtsfeiertag 🎉",   date: new Date(year, 11, 25), states: MAP["1. Weihnachtsfeiertag"] },
                { name: "2. Weihnachtsfeiertag 🎉",   date: new Date(year, 11, 26), states: MAP["2. Weihnachtsfeiertag"] },
    
                { name: "Heilige Drei Könige 🎉",     date: new Date(year, 0, 6),  states: MAP["Heilige Drei Könige"] },
                { name: "Fronleichnam 🎉",            date: add(easter, 60),       states: MAP["Fronleichnam"] },
                { name: "Mariä Himmelfahrt 🎉",       date: new Date(year, 7, 15), states: MAP["Mariä Himmelfahrt"] },
                { name: "Reformationstag 🎉",         date: new Date(year, 9, 31), states: MAP["Reformationstag"] },
                { name: "Allerheiligen 🎉",           date: new Date(year, 10, 1), states: MAP["Allerheiligen"] }
            ];
    
            const bussbettag = (() => {
                const d = new Date(year, 10, 23);
                while (d.getDay() !== 3) d.setDate(d.getDate() - 1);
                return d;
            })();
    
            holidays.push({ name: "Buß- und Bettag 🎉", date: bussbettag, states: MAP["Buß- und Bettag"] });
            return holidays;
        } else if (LAND === "AT") {
            const MAP = HOLIDAYS_BY_STATE_AT;
    
            const holidays = [
                { name: "Neujahr 🎉",                 date: new Date(year, 0, 1),  states: MAP["Neujahr"] },
                { name: "Heilige Drei Könige 🎉",     date: new Date(year, 0, 6),  states: MAP["Heilige Drei Könige"] },
    
                { name: "Ostersonntag 🎉",            date: easter,                states: MAP["Ostersonntag"] },
                { name: "Ostermontag 🎉",             date: add(easter, 1),        states: MAP["Ostermontag"] },
    
                { name: "Staatsfeiertag 🎉",          date: new Date(year, 4, 1),  states: MAP["Staatsfeiertag"] },
                { name: "Christi Himmelfahrt 🎉",     date: add(easter, 39),       states: MAP["Christi Himmelfahrt"] },
                { name: "Pfingstsonntag 🎉",          date: add(easter, 49),       states: MAP["Pfingstsonntag"] },
                { name: "Pfingstmontag 🎉",           date: add(easter, 50),       states: MAP["Pfingstmontag"] },
                { name: "Fronleichnam 🎉",            date: add(easter, 60),       states: MAP["Fronleichnam"] },
    
                { name: "Mariä Himmelfahrt 🎉",       date: new Date(year, 7, 15), states: MAP["Mariä Himmelfahrt"] },
                { name: "Nationalfeiertag 🎉",        date: new Date(year, 9, 26), states: MAP["Nationalfeiertag"] },
                { name: "Allerheiligen 🎉",           date: new Date(year, 10, 1), states: MAP["Allerheiligen"] },
                { name: "Mariä Empfängnis 🎉",        date: new Date(year, 11, 8), states: MAP["Mariä Empfängnis"] },
                { name: "1. Weihnachtsfeiertag 🎉",   date: new Date(year, 11, 25), states: MAP["1. Weihnachtsfeiertag"] },
                { name: "2. Weihnachtsfeiertag 🎉",   date: new Date(year, 11, 26), states: MAP["2. Weihnachtsfeiertag"] },
    
                { name: "Josef 🎉",                   date: new Date(year, 2, 19), states: MAP["Josef"] },
                { name: "Florian 🎉",                 date: new Date(year, 4, 4),  states: MAP["Florian"] },
                { name: "Rupert 🎉",                  date: new Date(year, 8, 24), states: MAP["Rupert"] },
                { name: "Leopold 🎉",                 date: new Date(year, 10, 15), states: MAP["Leopold"] },
                { name: "Martin 🎉",                  date: new Date(year, 10, 11), states: MAP["Martin"] },
                { name: "Klemens 🎉",                 date: new Date(year, 10, 23), states: MAP["Klemens"] }
            ];
    
            return holidays;
        }
    
        return [];
    }
    
    function createHolidayEvents(year) {
        const list   = getHolidays(year, LAND);
        const events = [];
    
        for (const h of list) {
            if (!h.states.includes(Bundesland)) continue;
    
            const start = new Date(h.date.getFullYear(), h.date.getMonth(), h.date.getDate(), 0, 0);
            const end   = new Date(h.date.getFullYear(), h.date.getMonth(), h.date.getDate(), 23, 59);
    
            const norm = normalizeEvent({start, end, summary: h.name, description: "", location: "", datetype: "date"}, "Feiertag", "#880088", "");
            events.push(norm);
        }
    
        const sommer = lastSunday(year, 2);
        {
            const start = new Date(sommer.getFullYear(), sommer.getMonth(), sommer.getDate(), 0, 0);
            const end   = new Date(sommer.getFullYear(), sommer.getMonth(), sommer.getDate(), 23, 59);
    
            const norm = normalizeEvent({start, end, summary: "Beginn der Sommerzeit ☀️", description: "", location: "", datetype: "date"}, "Feiertag", "#ffaa00", "");
            events.push(norm);
        }
    
        const winter = lastSunday(year, 9);
        {
            const start = new Date(winter.getFullYear(), winter.getMonth(), winter.getDate(), 0, 0);
            const end   = new Date(winter.getFullYear(), winter.getMonth(), winter.getDate(), 23, 59);
    
            const norm = normalizeEvent({start, end, summary: "Beginn der Winterzeit ❄️", description: "", location: "", datetype: "date"}, "Feiertag", "#00aaff");
            events.push(norm);
        }
    
        return events;
    }
    
    function normalizeEvent(evt, owner, color, img) {
        let start = evt.start;
        let end   = evt.end;
    
        if (evt.datetype === 'date') {
            start = new Date(start.getFullYear(), start.getMonth(), start.getDate(), 0, 0);
            end   = new Date(start.getFullYear(), start.getMonth(), start.getDate(), 23, 59);
        }
    
        return {
            startTime: start,
            endTime: end,
            isFullDay: evt.datetype === 'date',
            durationHours: (end - start) / 3600000,
            event: evt.summary || "",
            section: evt.description || "",
            location: evt.location || "",
            calColor: color,
            classRaw: owner,
            img: img
        };
    }
    
    function renderEventC1(norm) {
        if (isInMemoryEvent(norm)) {
            norm.calColor = "#808080";
            norm.event   += " 🕯️";
        }
    
        let start = formatDate(norm.startTime, "YYYY-MM-DD HH:mm");
        let end   = formatDate(norm.endTime,   "YYYY-MM-DD HH:mm");
    
        if (norm.isFullDay) {
            start = formatDate(norm.startTime, "YYYY-MM-DD");
            end   = formatDate(norm.endTime,   "YYYY-MM-DD");
        }
    
        const isPast = norm.endTime < new Date();
        let bg  = hexToRgba(norm.calColor, 0.6);
        let txt = "#ffffff";
    
        if (isPast) {
            bg  = fadeColor(norm.calColor, 0.25);
            txt = "#dddddd";
        }
    
        const ce = getKeywordColorEmoji(norm.event, norm.calColor);
    
        return {start, end, name: norm.event + " " + norm.section + ce.emoji, color: bg, colorText: txt};
    }
    
    function renderEventM1(norm, filter) {
        const owner = norm.classRaw;
    
        if (owner === "Feiertag") return null;
        if (isInMemoryEvent(norm)) return null;
    
        if (!(filter === "ALLE" || owner === filter)) return null;
    
        let start = formatDate(norm.startTime, "DD.MM.YYYY HH:mm");
        let end   = formatDate(norm.endTime,   "HH:mm");
    
        if (norm.isFullDay) {
            start = formatDate(norm.startTime, "DD.MM.YYYY");
            end   = start;
        }
    
        let e = norm.event;
        if (norm.section) e += ": " + norm.section;
    
        return {
            img: norm.img,
            zeit: `${start} - ${end}`,
            ereignis: e,
            ort: norm.location
        };
    }
    
    async function loadICS(url) {
        try {
            const res = await axios.get(url);
            let txt = res.data;
            txt = txt.replace(/UNTIL=(\d{8})(?!T)/g, "UNTIL=$1T000000Z");
    
            const raw = ical.parseICS(txt);
            const events = [];
    
            const now        = new Date();
            const rangeStart = new Date(now.getTime() - C1_PAST_DAYS * 86400000);
            const rangeEnd   = new Date(now.getTime() + C1_FUTURE_DAYS * 86400000);
    
            for (const key in raw) {
    
                const e = raw[key];
                if (!e || e.type !== "VEVENT") continue;
    
                const summary    = e.summary || "";
                const isBirthday = /geburtstag/i.test(summary);
                const isInMemory = /in memory/i.test(summary) || /geb\.\s*opa/i.test(summary);
    
                if (isBirthday) {
                    events.push({start: e.start, end: e.end, summary: e.summary, description: e.description, location: e.location, datetype: e.datetype});
                    continue;
                }
    
                if (e.rrule && !isInMemory) {
                    const opts = e.rrule.origOptions || {};
                    if (opts.freq === "YEARLY" || opts.freq === 1) {
                        events.push({start: e.start, end: e.end, summary: e.summary, description: e.description, location: e.location, datetype: e.datetype});
                        continue;
                    }
    
                    let freq = opts.freq;
                    if (typeof freq === "string") freq = freq.toUpperCase();
    
                    if (
                        freq === "WEEKLY" ||
                        freq === "DAILY" ||
                        freq === 2 ||
                        freq === 3
                    ) {
                        const weekdayMap = {SU: 0, MO: 1, TU: 2, WE: 3, TH: 4, FR: 5, SA: 6};
                        let byweekday = [];
                        const rawWeekdays = opts.byweekday || opts.byDay || [];
    
                        if (Array.isArray(rawWeekdays)) {
                            byweekday = rawWeekdays
                                .map(w => {
                                    if (typeof w === "number") return w;
                                    if (typeof w === "string") return weekdayMap[w.toUpperCase()];
                                    if (typeof w === "object" && w.weekday !== undefined) return w.weekday;
                                    return null;
                                })
                                .filter(v => v !== null && v !== undefined);
                        }
    
                        const start = new Date(e.start);
                        const until = opts.until || rangeEnd;
    
                        let current = new Date(start);
                        while (current <= until && current <= rangeEnd) {
                            const weekday = current.getDay();
                            if (byweekday.length && !byweekday.includes(weekday)) {
                                current.setDate(current.getDate() + 1);
                                continue;
                            }
    
                            if (byweekday.length && (opts.interval || 1) > 1) {
                                const diffDays = Math.floor((current - start) / 86400000);
                                const currentWeek = Math.floor(diffDays / 7);
    
                                if (currentWeek % opts.interval !== 0) {
                                    current.setDate(current.getDate() + 1);
                                    continue;
                                }
                            }
    
                            if (current >= rangeStart) {
                                const instStart = new Date(current);
                                const instEnd   = new Date(instStart.getTime() + (e.end - e.start));
                                const iso = instStart.getFullYear() + "-" + String(instStart.getMonth() + 1).padStart(2, "0") + "-" + String(instStart.getDate()).padStart(2, "0");
    
                                if (!(e.exdate && e.exdate[iso])) {
                                    events.push({start: instStart, end: instEnd, summary: e.summary, description: e.description, location: e.location, datetype: e.datetype});
                                }
                            }
                            current.setDate(current.getDate() + 1);
                        }
                        continue;
                    }
                }
                events.push({start: e.start, end: e.end, summary: e.summary, description: e.description, location: e.location, datetype: e.datetype});
            }
            return events;
        } catch (err) {
            console.error("ICS Fehler >>> " + err.message + " : " + err.stack);
            return null;
        }
    }
    
    async function loadAllICS() {
        let events = [];
    
        for (const cal of CALENDARS) {
            const list = await loadICS(cal.url);
            if (!list) continue;
    
            for (const evt of list) {
                const norm = normalizeEvent(evt, cal.owner, cal.color, cal.img);
    
                const ce = getKeywordColorEmoji(norm.event, norm.calColor);
                norm.calColor = ce.color;
    
                events.push(norm);
            }
        }
    
        const yearNow  = new Date().getFullYear();
        const yearNext = yearNow + 1;
    
        events.push(...createHolidayEvents(yearNow));
        events.push(...createHolidayEvents(yearNext));
    
        events.sort((a, b) => a.startTime - b.startTime);
        cachedEvents = events;
    }
    
    function applyFilter() {
        const now = new Date();
    
        const M1_START = new Date(now.getTime() - M1_PAST_DAYS * 86400000);
        const M1_END   = new Date(now.getTime() + M1_FUTURE_DAYS * 86400000);
    
        const filter = String(getState(DP_INHABER)?.val || "ALLE").trim();
    
        const m1 = cachedEvents
            .filter(e => e.endTime >= M1_START && e.startTime <= M1_END)
            .map(e => renderEventM1(e, filter))
            .filter(Boolean);
    
        setStateIfChanged(DP_M1, m1);
    }
    
    function updateC1() {
        const now = new Date();
    
        const C1_START = new Date(now.getTime() - C1_PAST_DAYS * 86400000);
        const C1_END   = new Date(now.getTime() + C1_FUTURE_DAYS * 86400000);
    
        const c1 = cachedEvents
            .filter(e => e.endTime >= C1_START && e.startTime <= C1_END)
            .map(e => renderEventC1(e));
    
        setStateIfChanged(DP_C1, c1);
    }
    
    async function main() {
        await initStates();
        await loadAllICS();
        updateC1();
        applyFilter();
    }
    
    main();
    
    on({ id: DP_INHABER, change: "ne" }, () => applyFilter());
    
    schedule("*/5 * * * *", () => main());
    

    Das Kalender-Widget (Scounger materialdesign 0.5.94)

    [{"tpl":"tplVis-materialdesign-Calendar","data":{"oid":"0_userdata.0.calendar.c1","g_fixed":false,"g_visibility":false,"g_css_font_text":true,"g_css_background":false,"g_css_shadow_padding":false,"g_css_border":false,"g_gestures":false,"g_signals":false,"g_last_change":false,"visibility-cond":"==","visibility-val":1,"visibility-groups-action":"hide","calendarView":"month","vibrateOnMobilDevices":"50","calendarWeekdays":"1,2,3,4,5,6,0","calendarBorderColor":"#7fb5b5","calendarDayBackgroundColor":"#121212","calendarDayBackgroundOutsideColor":"#404040","calendarHeaderBackground":"#424242","calendarDayLabelFontSize":"14","calendarDayLabelFontFamily":"InterVariable","calendarDayLabelFontColor":"#efd01f","calendarDayLabelPreviousFontColor":"#12abab","calendarDayLabelTodayFontSize":"14","calendarDayLabelTodayFontFamily":"InterVariable","calendarDayLabelTodayFontColor":"#fa9300","calendarWeeksNumbersShow":true,"calendarWeeksNumbersBackground":"#12abab","calendarWeeksNumbersFontSize":"16","calendarWeeksNumbersFont":"InterVariable","calendarWeeksNumbersFontColor":"#ffffff","calendarDayButtonMonthViewGoTo":"week","calendarDayButtonWeekViewGoTo":"day","calendarDayButtonDayViewGoTo":"week","calendarDayButtonRippleEffectColor":"#mdwTheme:vis-materialdesign.0.colors.calendar.button_hover","calendarDayButtonColor":"#mdwTheme:vis-materialdesign.0.colors.calendar.button_background","calendarDayButtonFontSize":"16","calendarDayButtonFontFamily":"InterVariable","calendarDayButtonFontColor":"#ffffff","calendarDayButtonTodayColor":"#mdwTheme:vis-materialdesign.0.colors.calendar.button_background_today","calendarDayButtonTodayFontSize":"16","calendarDayButtonTodayFontFamily":"InterVariable","calendarDayButtonTodayFontColor":"#mdwTheme:vis-materialdesign.0.colors.calendar.button_text_today","controlShow":true,"controlButtonLayout":"outlined","controlPosition":"stretch","controlShowLabel":true,"controlButtonColor":"#a45119","controlButtonRippelEffectColor":"#mdwTheme:vis-materialdesign.0.colors.calendar.control_panel_hover","controlIconColor":"#ffffff","controlTextSize":"15","controlTextFont":"InterVariable","controlTextColor":"#ffffff","calendarTimeAxisBackgroundColor":"#424242","calendarTimeAxisShortIntervals":true,"calendarTimeAxisHeaderBackgroundColor":"#mdwTheme:vis-materialdesign.0.colors.calendar.time_axis_background_header","calendarTimeAxisFontSize":"#mdwTheme:vis-materialdesign.0.fontSizes.calendar.time_axis","calendarTimeAxisFont":"RobotoCondensed-Regular","calendarTimeAxisFontColor":"#fa9300","calendarEventOverlapMode":"stack","calendarEventFontSize":"14","calendarEventFont":"InterVariable","signals-cond-0":"==","signals-val-0":true,"signals-icon-0":"/vis/signals/lowbattery.png","signals-icon-size-0":0,"signals-blink-0":false,"signals-horz-0":0,"signals-vert-0":0,"signals-hide-edit-0":false,"signals-cond-1":"==","signals-val-1":true,"signals-icon-1":"/vis/signals/lowbattery.png","signals-icon-size-1":0,"signals-blink-1":false,"signals-horz-1":0,"signals-vert-1":0,"signals-hide-edit-1":false,"signals-cond-2":"==","signals-val-2":true,"signals-icon-2":"/vis/signals/lowbattery.png","signals-icon-size-2":0,"signals-blink-2":false,"signals-horz-2":0,"signals-vert-2":0,"signals-hide-edit-2":false,"lc-type":"last-change","lc-is-interval":true,"lc-is-moment":false,"lc-format":"","lc-position-vert":"top","lc-position-horz":"right","lc-offset-vert":0,"lc-offset-horz":0,"lc-font-size":"12px","lc-font-family":"","lc-font-style":"","lc-bkg-color":"","lc-color":"","lc-border-width":"0","lc-border-style":"","lc-border-color":"","lc-border-radius":10,"lc-zindex":0,"calendarShortWeekdays":false,"calendarTimeAxisStartTime":"4","calendarTimeAxisEndTime":"23","calendarTimeAxisIntervalMinutes":"","calendarEventHeight":"25","calendarTimeAxisWidth":"","controlIconSize":"30","g_calendarCustomFormats":false,"clickSoundVolume":"0.5"},"style":{"left":"0px","top":"100px","width":"1918px","height":"894px","z-index":"2","opacity":"{0_userdata.0.HardwareInfo.DashBoard.Opacity}%","text-shadow":"3px 3px 4px #000"},"widgetSet":"materialdesign"}]
    

    Das kleine Kalender-Widget (Scounger JSON / TABLE materialdesign 0.5.94)

    [{"tpl":"tplVis-materialdesign-Button-State","data":{"oid":"0_userdata.0.calendar.inhaber","g_fixed":false,"g_visibility":true,"g_css_font_text":false,"g_css_background":false,"g_css_shadow_padding":true,"g_css_border":true,"g_gestures":false,"g_signals":false,"g_last_change":false,"visibility-cond":"!=","visibility-val":"ALLE","visibility-groups-action":"hide","buttonStyle":"raised","vibrateOnMobilDevices":"50","buttontext":"","textFontFamily":"RobotoCondensed-Regular","textFontSize":"14","mdwButtonPrimaryColor":"rgba(31, 96, 196, 0.8)","mdwButtonSecondaryColor":"rgba(255,255,255,0.8)","signals-cond-0":"==","signals-val-0":true,"signals-icon-0":"/vis/signals/lowbattery.png","signals-icon-size-0":0,"signals-blink-0":false,"signals-horz-0":0,"signals-vert-0":0,"signals-hide-edit-0":false,"signals-cond-1":"==","signals-val-1":true,"signals-icon-1":"/vis/signals/lowbattery.png","signals-icon-size-1":0,"signals-blink-1":false,"signals-horz-1":0,"signals-vert-1":0,"signals-hide-edit-1":false,"signals-cond-2":"==","signals-val-2":true,"signals-icon-2":"/vis/signals/lowbattery.png","signals-icon-size-2":0,"signals-blink-2":false,"signals-horz-2":0,"signals-vert-2":0,"signals-hide-edit-2":false,"lc-type":"last-change","lc-is-interval":true,"lc-is-moment":false,"lc-format":"","lc-position-vert":"top","lc-position-horz":"right","lc-offset-vert":0,"lc-offset-horz":0,"lc-font-size":"12px","lc-font-family":"","lc-font-style":"","lc-bkg-color":"","lc-color":"","lc-border-width":"0","lc-border-style":"","lc-border-color":"","lc-border-radius":10,"lc-zindex":0,"value":"ALLE","g_lock":true,"autoLockAfter":"10","lockIconColor":"#mdwTheme:vis-materialdesign.0.colors.button.lock_icon","lockFilterGrayscale":"","labelWidth":"0","clickSoundVolume":"0.5","colorBgTrue":"","mdwButtonColorPress":"rgba(96, 22, 42, 0.8)","lockIconSize":"20","g_icon":true,"image":"/vis.0/Familie.png","iconPosition":"left","iconHeight":"42","visibility-oid":"0_userdata.0.calendar.inhaber"},"style":{"left":"1163px","top":"661px","z-index":"8","width":"44px","height":"44px","box-shadow":"3px 3px 8px 1px rgba(31, 96, 196, 0.45)","border-radius":"180px","border-width":"1px","border-color":"rgba(31, 96, 196, 0.8)","border-style":"solid"},"widgetSet":"materialdesign"},{"tpl":"tplVis-materialdesign-Button-State","data":{"oid":"0_userdata.0.calendar.inhaber","g_fixed":false,"g_visibility":false,"g_css_font_text":false,"g_css_background":false,"g_css_shadow_padding":true,"g_css_border":true,"g_gestures":false,"g_signals":false,"g_last_change":false,"buttonStyle":"raised","vibrateOnMobilDevices":"50","mdwButtonPrimaryColor":"rgba(96, 22, 42, 0.8)","mdwButtonSecondaryColor":"rgba(255,255,255,0.8)","signals-cond-0":"==","signals-val-0":true,"signals-icon-0":"/vis/signals/lowbattery.png","signals-icon-size-0":0,"signals-blink-0":false,"signals-horz-0":0,"signals-vert-0":0,"signals-hide-edit-0":false,"signals-cond-1":"==","signals-val-1":true,"signals-icon-1":"/vis/signals/lowbattery.png","signals-icon-size-1":0,"signals-blink-1":false,"signals-horz-1":0,"signals-vert-1":0,"signals-hide-edit-1":false,"signals-cond-2":"==","signals-val-2":true,"signals-icon-2":"/vis/signals/lowbattery.png","signals-icon-size-2":0,"signals-blink-2":false,"signals-horz-2":0,"signals-vert-2":0,"signals-hide-edit-2":false,"lc-type":"last-change","lc-is-interval":true,"lc-is-moment":false,"lc-format":"","lc-position-vert":"top","lc-position-horz":"right","lc-offset-vert":0,"lc-offset-horz":0,"lc-font-size":"12px","lc-font-family":"","lc-font-style":"","lc-bkg-color":"","lc-color":"","lc-border-width":"0","lc-border-style":"","lc-border-color":"","lc-border-radius":10,"lc-zindex":0,"value":"Loreen","g_lock":true,"autoLockAfter":"10","lockIconColor":"#mdwTheme:vis-materialdesign.0.colors.button.lock_icon","lockFilterGrayscale":"","clickSoundVolume":"0.5","colorBgTrue":"","mdwButtonColorPress":"rgba(96, 22, 42, 0.8)","lockIconSize":"20","g_icon":true,"image":"/vis.0/Mama.png","iconPosition":"left","iconHeight":"40","visibility-cond":"==","visibility-val":1,"visibility-groups-action":"hide","g_label":true,"buttontext":"","textFontFamily":"#mdwTheme:vis-materialdesign.0.fonts.button.default.text","textFontSize":"#mdwTheme:vis-materialdesign.0.fontSizes.button.default.text"},"style":{"left":"1220px","top":"661px","z-index":"8","width":"44px","height":"44px","box-shadow":"3px 3px 8px 1px rgba(96, 22, 42, 0.45)","border-radius":"180px","border-width":"1px","border-color":"rgba(96, 22, 42, 0.8)","border-style":"solid"},"widgetSet":"materialdesign"},{"tpl":"tplVis-materialdesign-Button-State","data":{"oid":"0_userdata.0.calendar.inhaber","g_fixed":false,"g_visibility":false,"g_css_font_text":false,"g_css_background":false,"g_css_shadow_padding":true,"g_css_border":true,"g_gestures":false,"g_signals":false,"g_last_change":false,"buttonStyle":"raised","vibrateOnMobilDevices":"50","buttontext":"","textFontFamily":"RobotoCondensed-Regular","textFontSize":"14","mdwButtonPrimaryColor":"rgba(55, 135, 45, 0.8)","mdwButtonSecondaryColor":"rgba(255,255,255,0.8)","signals-cond-0":"==","signals-val-0":true,"signals-icon-0":"/vis/signals/lowbattery.png","signals-icon-size-0":0,"signals-blink-0":false,"signals-horz-0":0,"signals-vert-0":0,"signals-hide-edit-0":false,"signals-cond-1":"==","signals-val-1":true,"signals-icon-1":"/vis/signals/lowbattery.png","signals-icon-size-1":0,"signals-blink-1":false,"signals-horz-1":0,"signals-vert-1":0,"signals-hide-edit-1":false,"signals-cond-2":"==","signals-val-2":true,"signals-icon-2":"/vis/signals/lowbattery.png","signals-icon-size-2":0,"signals-blink-2":false,"signals-horz-2":0,"signals-vert-2":0,"signals-hide-edit-2":false,"lc-type":"last-change","lc-is-interval":true,"lc-is-moment":false,"lc-format":"","lc-position-vert":"top","lc-position-horz":"right","lc-offset-vert":0,"lc-offset-horz":0,"lc-font-size":"12px","lc-font-family":"","lc-font-style":"","lc-bkg-color":"","lc-color":"","lc-border-width":"0","lc-border-style":"","lc-border-color":"","lc-border-radius":10,"lc-zindex":0,"value":"Yasu","g_lock":true,"autoLockAfter":"10","lockIconColor":"#mdwTheme:vis-materialdesign.0.colors.button.lock_icon","lockFilterGrayscale":"","labelWidth":"0","clickSoundVolume":"0.5","colorBgTrue":"","mdwButtonColorPress":"rgba(96, 22, 42, 0.8)","lockIconSize":"20","g_icon":true,"image":"/vis.0/Kind2.png","iconPosition":"left","iconHeight":"40","visibility-cond":"==","visibility-val":1,"visibility-groups-action":"hide"},"style":{"left":"1277px","top":"661px","z-index":"8","width":"44px","height":"44px","box-shadow":"3px 3px 8px 1px rgba(55, 135, 45, 0.45)","border-radius":"180px","border-width":"1px","border-color":"rgba(55, 135, 45, 0.8)","border-style":"solid"},"widgetSet":"materialdesign"},{"tpl":"tplVis-materialdesign-Button-State","data":{"oid":"0_userdata.0.calendar.inhaber","g_fixed":false,"g_visibility":false,"g_css_font_text":false,"g_css_background":false,"g_css_shadow_padding":true,"g_css_border":true,"g_gestures":false,"g_signals":false,"g_last_change":false,"buttonStyle":"raised","vibrateOnMobilDevices":"50","buttontext":"","textFontFamily":"RobotoCondensed-Regular","textFontSize":"14","mdwButtonPrimaryColor":"rgba(224, 180, 0, 0.8)","mdwButtonSecondaryColor":"rgba(255,255,255,0.8)","signals-cond-0":"==","signals-val-0":true,"signals-icon-0":"/vis/signals/lowbattery.png","signals-icon-size-0":0,"signals-blink-0":false,"signals-horz-0":0,"signals-vert-0":0,"signals-hide-edit-0":false,"signals-cond-1":"==","signals-val-1":true,"signals-icon-1":"/vis/signals/lowbattery.png","signals-icon-size-1":0,"signals-blink-1":false,"signals-horz-1":0,"signals-vert-1":0,"signals-hide-edit-1":false,"signals-cond-2":"==","signals-val-2":true,"signals-icon-2":"/vis/signals/lowbattery.png","signals-icon-size-2":0,"signals-blink-2":false,"signals-horz-2":0,"signals-vert-2":0,"signals-hide-edit-2":false,"lc-type":"last-change","lc-is-interval":true,"lc-is-moment":false,"lc-format":"","lc-position-vert":"top","lc-position-horz":"right","lc-offset-vert":0,"lc-offset-horz":0,"lc-font-size":"12px","lc-font-family":"","lc-font-style":"","lc-bkg-color":"","lc-color":"","lc-border-width":"0","lc-border-style":"","lc-border-color":"","lc-border-radius":10,"lc-zindex":0,"value":"Ronny","g_lock":true,"autoLockAfter":"10","lockIconColor":"#mdwTheme:vis-materialdesign.0.colors.button.lock_icon","lockFilterGrayscale":"","labelWidth":"0","clickSoundVolume":"0.5","colorBgTrue":"","mdwButtonColorPress":"rgba(96, 22, 42, 0.8)","lockIconSize":"20","g_icon":true,"image":"/vis.0/Papa.png","iconPosition":"left","iconHeight":"40","visibility-cond":"==","visibility-val":1,"visibility-groups-action":"hide"},"style":{"left":"1334px","top":"661px","z-index":"8","width":"44px","height":"44px","box-shadow":"3px 3px 8px 1px rgba(224, 180, 0, 0.45)","border-radius":"180px","border-width":"1px","border-color":"rgba(224, 180, 0, 0.8)","border-style":"solid"},"widgetSet":"materialdesign"},{"tpl":"tplVis-materialdesign-Table","data":{"oid":"0_userdata.0.calendar.m1","g_fixed":false,"g_visibility":false,"g_css_font_text":true,"g_css_background":false,"g_css_shadow_padding":false,"g_css_border":false,"g_gestures":false,"g_signals":false,"g_last_change":false,"countCols":"3","tableLayout":"card","showHeader":false,"roundBorder":"true","headerTextSize":"#mdwTheme:vis-materialdesign.0.fontSizes.table.header","headerFontFamily":"#mdwTheme:vis-materialdesign.0.fonts.table.header","colorBackground":"transparent","colorHeaderRowBackground":"","colorHeaderRowText":"","colorRowBackground":"transparent","colorRowBackgroundHover":"transparent","colorRowText":"rgba(255,255,255,0.8)","borderColor":"transparent","dividers":"rgba(110, 70, 62, 0.25)","showColumn0":"true","colType0":"image","textAlign0":"left","colTextSize0":"#mdwTheme:vis-materialdesign.0.fontSizes.table.row","fontFamily0":"#mdwTheme:vis-materialdesign.0.fonts.table.row","showColumn1":"true","colType1":"text","textAlign1":"left","colTextSize1":"14","fontFamily1":"RobotoCondensed-Regular","signals-cond-0":"==","signals-val-0":true,"signals-icon-0":"/vis/signals/lowbattery.png","signals-icon-size-0":0,"signals-blink-0":false,"signals-horz-0":0,"signals-vert-0":0,"signals-hide-edit-0":false,"signals-cond-1":"==","signals-val-1":true,"signals-icon-1":"/vis/signals/lowbattery.png","signals-icon-size-1":0,"signals-blink-1":false,"signals-horz-1":0,"signals-vert-1":0,"signals-hide-edit-1":false,"signals-cond-2":"==","signals-val-2":true,"signals-icon-2":"/vis/signals/lowbattery.png","signals-icon-size-2":0,"signals-blink-2":false,"signals-horz-2":0,"signals-vert-2":0,"signals-hide-edit-2":false,"lc-type":"last-change","lc-is-interval":true,"lc-is-moment":false,"lc-format":"","lc-position-vert":"top","lc-position-horz":"right","lc-offset-vert":0,"lc-offset-horz":0,"lc-font-size":"12px","lc-font-family":"","lc-font-style":"","lc-bkg-color":"","lc-color":"","lc-border-width":"0","lc-border-style":"","lc-border-color":"","lc-border-radius":10,"lc-zindex":0,"dataJson":"","showColumn2":"true","colType2":"text","textAlign2":"left","colTextSize2":"14","fontFamily2":"RobotoCondensed-Regular","showColumn3":"true","colType3":"text","textAlign3":"left","colTextSize3":"14","fontFamily3":"RobotoCondensed-Regular","showColumn4":"true","colType4":"text","textAlign4":"center","colTextSize4":"#mdwTheme:vis-materialdesign.0.fontSizes.table.row","fontFamily4":"#mdwTheme:vis-materialdesign.0.fonts.table.row","imageSize0":"40","label0":" ","colNoWrap1":false,"columnWidth0":"58","colNoWrap0":true,"fixedHeader":true,"columnWidth1":"110","rowHeight":"55","columnWidth2":"200","visibility-cond":"==","visibility-val":1,"visibility-groups-action":"hide","padding_left2":"0","padding_right2":"0","padding_left1":"5","padding_right1":"5","padding_left3":"0","padding_right3":"5"},"style":{"left":"664px","top":"695px","width":"732px","height":"284px","z-index":"8","text-shadow":"3px 3px 4px #000"},"widgetSet":"materialdesign"},{"tpl":"tplFrame","data":{"g_fixed":true,"g_visibility":false,"g_css_font_text":true,"g_css_background":true,"g_css_shadow_padding":true,"g_css_border":true,"g_gestures":false,"g_signals":false,"g_last_change":false,"visibility-cond":"==","visibility-val":1,"visibility-groups-action":"hide","title":"Familien - Kalender","title_color":"#ffffff","title_top":"2","title_left":"5","header_height":"23","header_color":"rgba(60, 75, 100, 0.10)","signals-cond-0":"==","signals-val-0":true,"signals-icon-0":"/vis/signals/lowbattery.png","signals-icon-size-0":0,"signals-blink-0":false,"signals-horz-0":0,"signals-vert-0":0,"signals-hide-edit-0":false,"signals-cond-1":"==","signals-val-1":true,"signals-icon-1":"/vis/signals/lowbattery.png","signals-icon-size-1":0,"signals-blink-1":false,"signals-horz-1":0,"signals-vert-1":0,"signals-hide-edit-1":false,"signals-cond-2":"==","signals-val-2":true,"signals-icon-2":"/vis/signals/lowbattery.png","signals-icon-size-2":0,"signals-blink-2":false,"signals-horz-2":0,"signals-vert-2":0,"signals-hide-edit-2":false,"lc-type":"last-change","lc-is-interval":true,"lc-is-moment":false,"lc-format":"","lc-position-vert":"top","lc-position-horz":"right","lc-offset-vert":0,"lc-offset-horz":0,"lc-font-size":"12px","lc-font-family":"","lc-font-style":"","lc-bkg-color":"","lc-color":"","lc-border-width":"0","lc-border-style":"","lc-border-color":"","lc-border-radius":10,"lc-zindex":0,"title_back":"transparent","name":"Rahmen","locked":false,"comment":"","class":"","filterkey":"","title_font":""},"style":{"left":"654px","top":"666px","z-index":"1","width":"750px","height":"319px","border-width":"1px","border-style":"solid","border-color":"rgba(60, 75, 100, 0.10)","border-radius":"10px","opacity":"{0_userdata.0.HardwareInfo.DashBoard.Opacity}%","box-shadow":"15px 18px 62px 0px rgba(0, 0, 0, 0.35)","font-family":"RobotoCondensed-Regular","font-style":"normal","font-variant":"small-caps","font-weight":"lighter","font-size":"medium","background":"#202020","background-color":"#000000","cursor":"","transform":"","color":"","text-shadow":"","line-height":"","letter-spacing":"","word-spacing":""},"widgetSet":"basic"}]
    

    Ich wünsche Euch viel Spaß und Erfolg bei der Umsetzung des Projekts.

    Ro75.

    Eduard77E Online
    Eduard77E Online
    Eduard77
    schrieb zuletzt editiert von
    #24

    @Ro75
    Vielen Dank für dein Skript.
    Die Integration hat bei mir geklappt und die DP werden befüllt.

    Leider nutze ich die VIS2 und muss jetzt schauen ob man dort sowas auch bauen kann, oder @oliverio macht was hübsches 😊.

    1 Antwort Letzte Antwort
    0
    • Ro75R Online
      Ro75R Online
      Ro75
      schrieb zuletzt editiert von
      #25

      Ist für mich nicht nachvollziehbar. Habe ehrlich gesagt im Moment keine Idee mehr. Was passiert wenn du mal einen Geburtstag, wie beschrieben, in den "normalen Kalender" einträgst. Da die Aktualisierung aller 5 Minuten ist (15:35, 15:40), müsstest du ja dann relativ schnell was sehen.

      Ro75.

      SERVER = Beelink U59 16GB DDR4 RAM 512GB SSD, FB 7490, FritzDect 200+301+440, ConBee II, Zigbee Aqara Sensoren + NOUS A1Z, NOUS A1T, Philips Hue ** ioBroker, REDIS, influxdb2, Grafana, PiHole, Plex-Mediaserver, paperless-ngx (Docker), MariaDB + phpmyadmin *** VIS-Runtime = Intel NUC 8GB RAM 128GB SSD + 24" Touchscreen

      NashraN 1 Antwort Letzte Antwort
      0
      • Ro75R Ro75

        Ist für mich nicht nachvollziehbar. Habe ehrlich gesagt im Moment keine Idee mehr. Was passiert wenn du mal einen Geburtstag, wie beschrieben, in den "normalen Kalender" einträgst. Da die Aktualisierung aller 5 Minuten ist (15:35, 15:40), müsstest du ja dann relativ schnell was sehen.

        Ro75.

        NashraN Offline
        NashraN Offline
        Nashra
        Most Active Forum Testing
        schrieb zuletzt editiert von
        #26

        @Ro75 sagte:

        Ist für mich nicht nachvollziehbar. Habe ehrlich gesagt im Moment keine Idee mehr. Was passiert wenn du mal einen Geburtstag, wie beschrieben, in den "normalen Kalender" einträgst. Da die Aktualisierung aller 5 Minuten ist (15:35, 15:40), müsstest du ja dann relativ schnell was sehen.

        Ro75.

        Hatte ich gerade schon gemacht 😁
        Wird angezeigt.

        Ich lege die Kalender bei Google mal neu an, mal sehen ob es dann klappt.

        Gruß Ralf
        Mir egal, wer Dein Vater ist! Wenn ich hier angel, wird nicht übers Wasser gelaufen!!

        Benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat.

        1 Antwort Letzte Antwort
        1
        • NashraN Offline
          NashraN Offline
          Nashra
          Most Active Forum Testing
          schrieb zuletzt editiert von Nashra
          #27

          Fehler wohl gefunden, es liegt an den Einträgen bei Google.
          Habe mal einen Geburtstag komplett gelöscht und dann neu angelegt und wieder mit der Angabe jedes Jahr.
          Wird jetzt angezeigt, schwere Geburt bzw. keine Ahnung was Google da für ein Problem hatte.
          Werde jetzt alle Daten komplett löschen und neu eingeben.

          Gruß Ralf
          Mir egal, wer Dein Vater ist! Wenn ich hier angel, wird nicht übers Wasser gelaufen!!

          Benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat.

          1 Antwort Letzte Antwort
          1
          • Ro75R Online
            Ro75R Online
            Ro75
            schrieb zuletzt editiert von
            #28

            Super, dass das Problem gefunden wurde. Google ändert hin und wieder was. Das letzte Mal vor ein paar Wochen. Da gab es Änderungen bei wiederkehrenden Terminen. Beim Google Kalender selbst kein Problem, aber es gab geänderte Infos in der ICS. Aber da ist bereits im Script berücksichtigt.

            Ro75.

            SERVER = Beelink U59 16GB DDR4 RAM 512GB SSD, FB 7490, FritzDect 200+301+440, ConBee II, Zigbee Aqara Sensoren + NOUS A1Z, NOUS A1T, Philips Hue ** ioBroker, REDIS, influxdb2, Grafana, PiHole, Plex-Mediaserver, paperless-ngx (Docker), MariaDB + phpmyadmin *** VIS-Runtime = Intel NUC 8GB RAM 128GB SSD + 24" Touchscreen

            1 Antwort Letzte Antwort
            0
            • NashraN Offline
              NashraN Offline
              Nashra
              Most Active Forum Testing
              schrieb zuletzt editiert von
              #29

              Ist echt ein Witz. Jahrelang keine Probleme mit Anzeige und dann ändern die was ohne
              das man was mitbekommt. Naja, habe jetzt einige Termine in den jeweiligen Kalendern
              gelöscht und eben neu angelegt, jetzt klappt es.

              Vielen Dank an Dich für die Hilfe.

              Gruß Ralf
              Mir egal, wer Dein Vater ist! Wenn ich hier angel, wird nicht übers Wasser gelaufen!!

              Benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat.

              1 Antwort Letzte Antwort
              1
              • Ro75R Online
                Ro75R Online
                Ro75
                schrieb zuletzt editiert von
                #30

                @nashra Ich danke Dir dafür!

                Ro75.

                SERVER = Beelink U59 16GB DDR4 RAM 512GB SSD, FB 7490, FritzDect 200+301+440, ConBee II, Zigbee Aqara Sensoren + NOUS A1Z, NOUS A1T, Philips Hue ** ioBroker, REDIS, influxdb2, Grafana, PiHole, Plex-Mediaserver, paperless-ngx (Docker), MariaDB + phpmyadmin *** VIS-Runtime = Intel NUC 8GB RAM 128GB SSD + 24" Touchscreen

                NashraN 1 Antwort Letzte Antwort
                0
                • Ro75R Ro75

                  @nashra Ich danke Dir dafür!

                  Ro75.

                  NashraN Offline
                  NashraN Offline
                  Nashra
                  Most Active Forum Testing
                  schrieb zuletzt editiert von
                  #31

                  @Ro75 sagte:

                  @nashra Ich danke Dir dafür!

                  Ro75.

                  Nix zu danken, mir ist geholfen und Du weißt beim nächsten User woran es liegt.

                  Aber einen habe ich noch...
                  Die Hintergrundfarbe für "In Memory", wo finde ich die?

                  Gruß Ralf
                  Mir egal, wer Dein Vater ist! Wenn ich hier angel, wird nicht übers Wasser gelaufen!!

                  Benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat.

                  Ro75R 1 Antwort Letzte Antwort
                  0
                  • NashraN Nashra

                    @Ro75 sagte:

                    @nashra Ich danke Dir dafür!

                    Ro75.

                    Nix zu danken, mir ist geholfen und Du weißt beim nächsten User woran es liegt.

                    Aber einen habe ich noch...
                    Die Hintergrundfarbe für "In Memory", wo finde ich die?

                    Ro75R Online
                    Ro75R Online
                    Ro75
                    schrieb zuletzt editiert von
                    #32

                    @Nashra sagte:

                    Die Hintergrundfarbe für "In Memory", wo finde ich die?

                    Wenn das jetzt keine separater Kalender ist, dann:

                    function renderEventC1(norm) {
                        if (isInMemoryEvent(norm)) {
                            norm.calColor = "#808080";
                            norm.event   += " 🕯️";
                        }
                    

                    Wenn eigenständiger Kalender: dann auch in der Kalender-Konfig

                            color: "#a71b1b", // Standardfarbe des Kalenders - großer Kalender (materialdesign - Scrounger)
                            img: BASE_URL + "Mama.png" // Icon für Darstellung - kleiner Kalender
                        },
                    

                    schauen.

                    Ro75.

                    SERVER = Beelink U59 16GB DDR4 RAM 512GB SSD, FB 7490, FritzDect 200+301+440, ConBee II, Zigbee Aqara Sensoren + NOUS A1Z, NOUS A1T, Philips Hue ** ioBroker, REDIS, influxdb2, Grafana, PiHole, Plex-Mediaserver, paperless-ngx (Docker), MariaDB + phpmyadmin *** VIS-Runtime = Intel NUC 8GB RAM 128GB SSD + 24" Touchscreen

                    sigi234S 1 Antwort Letzte Antwort
                    1
                    • Ro75R Ro75

                      @Nashra sagte:

                      Die Hintergrundfarbe für "In Memory", wo finde ich die?

                      Wenn das jetzt keine separater Kalender ist, dann:

                      function renderEventC1(norm) {
                          if (isInMemoryEvent(norm)) {
                              norm.calColor = "#808080";
                              norm.event   += " 🕯️";
                          }
                      

                      Wenn eigenständiger Kalender: dann auch in der Kalender-Konfig

                              color: "#a71b1b", // Standardfarbe des Kalenders - großer Kalender (materialdesign - Scrounger)
                              img: BASE_URL + "Mama.png" // Icon für Darstellung - kleiner Kalender
                          },
                      

                      schauen.

                      Ro75.

                      sigi234S Online
                      sigi234S Online
                      sigi234
                      Forum Testing Most Active
                      schrieb zuletzt editiert von
                      #33

                      @Ro75
                      Habe jetzt nicht alles gelesen, was ist der Fehler?

                      Bitte benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat.
                      Immer Daten sichern!

                      1 Antwort Letzte Antwort
                      0
                      • Ro75R Online
                        Ro75R Online
                        Ro75
                        schrieb zuletzt editiert von
                        #34

                        Die Google Kalender hatten ein Problem. Bei @nashra hat die Neuanlage geholfen.

                        Ro75.

                        SERVER = Beelink U59 16GB DDR4 RAM 512GB SSD, FB 7490, FritzDect 200+301+440, ConBee II, Zigbee Aqara Sensoren + NOUS A1Z, NOUS A1T, Philips Hue ** ioBroker, REDIS, influxdb2, Grafana, PiHole, Plex-Mediaserver, paperless-ngx (Docker), MariaDB + phpmyadmin *** VIS-Runtime = Intel NUC 8GB RAM 128GB SSD + 24" Touchscreen

                        1 Antwort Letzte Antwort
                        0

                        Hey! Du scheinst an dieser Unterhaltung interessiert zu sein, hast aber noch kein Konto.

                        Hast du es satt, bei jedem Besuch durch die gleichen Beiträge zu scrollen? Wenn du dich für ein Konto anmeldest, kommst du immer genau dorthin zurück, wo du zuvor warst, und kannst dich über neue Antworten benachrichtigen lassen (entweder per E-Mail oder Push-Benachrichtigung). Du kannst auch Lesezeichen speichern und Beiträge positiv bewerten, um anderen Community-Mitgliedern deine Wertschätzung zu zeigen.

                        Mit deinem Input könnte dieser Beitrag noch besser werden 💗

                        Registrieren Anmelden
                        Antworten
                        • In einem neuen Thema antworten
                        Anmelden zum Antworten
                        • Älteste zuerst
                        • Neuste zuerst
                        • Meiste Stimmen


                        Support us

                        ioBroker
                        Community Adapters
                        Donate

                        302

                        Online

                        33.0k

                        Benutzer

                        83.4k

                        Themen

                        1.3m

                        Beiträge
                        Community
                        Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen | Einwilligungseinstellungen
                        ioBroker Community 2014-2026
                        logo
                        • Anmelden

                        • Du hast noch kein Konto? Registrieren

                        • Anmelden oder registrieren, um zu suchen
                        • Erster Beitrag
                          Letzter Beitrag
                        0
                        • Home
                        • Aktuell
                        • Tags
                        • Ungelesen 0
                        • Kategorien
                        • Unreplied
                        • Beliebt
                        • GitHub
                        • Docu
                        • Hilfe