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 190 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 Online
    Ro75R Online
    Ro75
    schrieb am zuletzt editiert von Ro75
    #1

    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.

    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

    OliverIOO Eduard77E 2 Antworten Letzte Antwort
    1
    • 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.

      OliverIOO Offline
      OliverIOO Offline
      OliverIO
      schrieb am zuletzt editiert von
      #2

      @Ro75

      JSON oder Tabellen Widget für kleinen Kalender
      @oliverio hier könntest du eventuell über vis-jsontemplate noch etwas beitragen

      Was würdest du dir denn da genau vorstellen?

      Meine Adapter und Widgets
      TVProgram, SqueezeboxRPC, OpenLiga, RSSFeed, MyTime,, pi-hole2, vis-json-template, skiinfo, vis-mapwidgets, vis-2-widgets-rssfeed
      Links im Profil

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

        Naja irgendwas passendes für vis-1 und vis-2. da ja materidesign unter vis-2 nicht läuft. Du hattest ja für das Telefonbuch auch etwas geniales gezaubert.

        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
        0
        • Ro75R Ro75

          Naja irgendwas passendes für vis-1 und vis-2. da ja materidesign unter vis-2 nicht läuft. Du hattest ja für das Telefonbuch auch etwas geniales gezaubert.

          Ro75.

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

          @Ro75 sagte:

          Naja irgendwas passendes für vis-1 und vis-2. da ja materidesign unter vis-2 nicht läuft. Du hattest ja für das Telefonbuch auch etwas geniales gezaubert.

          Ro75.

          Ja für VIS-2 schaut hier noch ein wenig fad aus.

          Screenshot (2954).png

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

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

            Moin,
            Erstmal, gefällt mir sehr gut der Kalender.
            Zum Problem: Habe 4 Kalender (Termine, Müll, Geburtstage, Todestage)
            Termine und Müll zeigt er an, aber die anderen zwei leider nicht.
            Es wird auch kein Fehler oder so angezeigt.
            Komme einfach nicht dahinter warum.

            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.

            sigi234S 1 Antwort Letzte Antwort
            0
            • NashraN Nashra

              Moin,
              Erstmal, gefällt mir sehr gut der Kalender.
              Zum Problem: Habe 4 Kalender (Termine, Müll, Geburtstage, Todestage)
              Termine und Müll zeigt er an, aber die anderen zwei leider nicht.
              Es wird auch kein Fehler oder so angezeigt.
              Komme einfach nicht dahinter warum.

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

              @Nashra sagte:

              Termine und Müll zeigt er an, aber die anderen zwei leider nicht.

              Ist auch bei mir so.

              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 Ro75
                #7

                @nashra @sigi234 zeigt mal bitte den Konfigurationsteil. Die url mit .ics bitte nicht vollständig weglassen, sondern nur Teile aus XXXX.

                also

                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)
                

                und const CALENDARS = ....

                Ro75.

                EDIT: Was steht im Datenpunkt "Inhaber" drin?

                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 Nashra
                  #8
                  const C1_PAST_DAYS   = 30;  // Anzahl Tage rückwärts für großen Kalender (materialdesign - Scrounger) (Vergangenheit)
                  const C1_FUTURE_DAYS = 180; // 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)
                  
                  
                      {
                          owner: "Geburtstag", // Besitzer/Zuordnung - Nach dieser Bezeichnung kann auch ein Filter ("0_userdata.0.calendar.inhaber") gesetzt werden
                          url: "https://calendar.google.com/calendar/ical/xxxxxxxxxxxxxxx%40group.calendar.google.com/private-xxxxxxxxxxxxxxxx/basic.ics", // private ICS-URL
                          color: "#e4c441", //#a71b1b Standardfarbe des Kalenders - großer Kalender (materialdesign - Scrounger)
                          img: BASE_URL + "geb.png" // Icon für Darstellung - kleiner Kalender
                      },
                  	
                  	{
                          owner: "In Memory",
                          url: "https://calendar.google.com/calendar/ical/xxxxxxxxxxxxxxxxxxxx%40group.calendar.google.com/private-xxxxxxxxxxxxxxxx/basic.ics",
                          color: "#039be5",
                          img: BASE_URL + "memory.png"
                      }
                  

                  im DP Inhaber = ALLE

                  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
                  0
                  • Ro75R Online
                    Ro75R Online
                    Ro75
                    schrieb zuletzt editiert von
                    #9

                    @nashra zeige mal bitte komplett const CALENDARS, NATÜRLICH XXXXXXXXX! Bei owner mal auf Leerzeichen verzichten.

                    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
                      #10

                      Hatte gerade mal die Owner geändert, aber nix

                      const CALENDARS = [
                          {
                              owner: "Termine",
                              url: "https://calendar.google.com/calendar/ical/xxx%40gmail.com/private-xxx/basic.ics",
                              color: "#039be5",
                              img: BASE_URL + "termine.png"
                          },
                          {
                              owner: "Geburt", // Besitzer/Zuordnung - Nach dieser Bezeichnung kann auch ein Filter ("0_userdata.0.calendar.inhaber") gesetzt werden
                              url: "https://calendar.google.com/calendar/ical/xxx%40group.calendar.google.com/private-xxx/basic.ics", // private ICS-URL
                              color: "#e4c441", //#a71b1b Standardfarbe des Kalenders - großer Kalender (materialdesign - Scrounger)
                              img: BASE_URL + "geb.png" // Icon für Darstellung - kleiner Kalender
                          },
                          {
                              owner: "Muell",
                              url: "https://calendar.google.com/calendar/ical/xxx%40group.calendar.google.com/private-xxx/basic.ics",
                              color: "#e4c441",
                              img: BASE_URL + "muell.png"
                          },
                          {
                              owner: "Tod",
                              url: "https://calendar.google.com/calendar/ical/xxx%40group.calendar.google.com/private-xxx/basic.ics",
                              color: "#039be5",
                              img: BASE_URL + "memory.png"
                          }
                      ];
                      

                      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
                      0
                      • Ro75R Online
                        Ro75R Online
                        Ro75
                        schrieb zuletzt editiert von Ro75
                        #11

                        Sieht eigentlich gut aus. Lassen sich die URL's im Browser öffnen? Wenn ja, müsste ein "speichern unter" kommen.

                        Ro75.

                        EDIT: Nutze auch unterschiedliche Farben.

                        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

                          Sieht eigentlich gut aus. Lassen sich die URL's im Browser öffnen? Wenn ja, müsste ein "speichern unter" kommen.

                          Ro75.

                          EDIT: Nutze auch unterschiedliche Farben.

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

                          @Ro75 sagte:

                          Sieht eigentlich gut aus. Lassen sich die URL's im Browser öffnen? Wenn ja, müsste ein "speichern unter" kommen.

                          Ro75.

                          Lassen sich aufrufen und werden sofort gespeichert

                          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
                          0
                          • Ro75R Online
                            Ro75R Online
                            Ro75
                            schrieb zuletzt editiert von
                            #13

                            Nutze auch unterschiedliche Farben.

                            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
                            • Ro75R Online
                              Ro75R Online
                              Ro75
                              schrieb zuletzt editiert von
                              #14

                              @nashra In welche json hast du mal reingeschaut?

                              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 In welche json hast du mal reingeschaut?

                                Ro75.

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

                                @Ro75 sagte:

                                @nashra In welche json hast du mal reingeschaut?

                                Ro75.

                                Äh, nutze c1. Termine und Müll drin, nur nicht die anderen zwei

                                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
                                0
                                • Ro75R Online
                                  Ro75R Online
                                  Ro75
                                  schrieb zuletzt editiert von Ro75
                                  #16

                                  Bei mir laufen 3 Kalender seit über einem Jahr mit diesem Script.

                                  ABER ich habe keine separate Kalender für Geburts- und Todestage. Deine Bezeichner für Geburt und Tod sind interne Filter.

                                  Bei uns laufen diese Termine über den normalen Kalender.

                                  Also Geburtstag Else oder Geburtstag Bernd. Oder Todestag Opa.

                                  Versuche mal einfach die owner in "sinnfrei Bezeichnungen" zu ändern. Test1 und Test2.

                                  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 Nashra
                                    #17

                                    Bezeichnung geändert in Test1 und Test2, leider nein
                                    Seltsam ist ja auch das zwei funktionieren und die anderen zwei nicht

                                    EDIT: habe mal nur den Geburtstag genommen und die anderen gelöscht, zeigt nix an

                                    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
                                    0
                                    • Ro75R Online
                                      Ro75R Online
                                      Ro75
                                      schrieb zuletzt editiert von
                                      #18

                                      Wieviel Geburtstage müssten denn die nächsten 180Tage kommen (C1)? Speichere doch mal die ics Datei und schaue mal mit dem Editor rein. Steht da was brauchbares drin? Wenn ja, wie sind diese Termine angelegt?

                                      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

                                        Wieviel Geburtstage müssten denn die nächsten 180Tage kommen (C1)? Speichere doch mal die ics Datei und schaue mal mit dem Editor rein. Steht da was brauchbares drin? Wenn ja, wie sind diese Termine angelegt?

                                        Ro75.

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

                                        @Ro75 sagte:

                                        Wieviel Geburtstage müssten denn die nächsten 180Tage kommen (C1)?

                                        Müßte ich jetzt zählen, die 180 sind eigentlich nur für die normalen Termine

                                        Speichere doch mal die ics Datei und schaue mal mit dem Editor rein. Steht da was brauchbares drin? Wenn ja, wie sind diese Termine angelegt?

                                        Ro75.

                                        BEGIN:VCALENDAR
                                        PRODID:-//Google Inc//Google Calendar 70.9054//EN
                                        VERSION:2.0
                                        CALSCALE:GREGORIAN
                                        METHOD:PUBLISH
                                        X-WR-CALNAME:Geburtstage
                                        X-WR-TIMEZONE:Europe/Berlin
                                        BEGIN:VEVENT
                                        DTSTART;VALUE=DATE:20260715
                                        DTEND;VALUE=DATE:20260716
                                        DTSTAMP:20260721T130410Z
                                        UID:xxxxxxx@google.com
                                        RECURRENCE-ID;VALUE=DATE:20260715
                                        CREATED:20230706T175253Z
                                        DESCRIPTION:1952
                                        LAST-MODIFIED:20260721T130210Z
                                        SEQUENCE:0
                                        STATUS:CONFIRMED
                                        SUMMARY:Geburtstag Reiner
                                        TRANSP:TRANSPARENT
                                        END:VEVENT
                                        

                                        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
                                        0
                                        • Ro75R Online
                                          Ro75R Online
                                          Ro75
                                          schrieb zuletzt editiert von
                                          #20

                                          Also die 180 Tage gelten für alle Kalender bei C1, 30 Tage bei M1, wobei dieser Termin ja nur am 15.07.26 war. von daher müsste er in C1 sein, aber nicht in M1. M1 nicht, weil da eine 0 bei vergangenen Tagen steht.

                                          Kannst du das bitte nochmal prüfen.

                                          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

                                          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

                                          255

                                          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