import { eventUrl, loadCalendar, occurrencesBetween } from "./events.js"; /** * When an occurrence starts, as a point in time. * * A date has no time and no zone, its digits are the day itself. toJSDate() * reads them as midnight in the zone of the browser, which moves an all day * event by the offset that zone has to Berlin and, far enough east or west, * onto the day before or after. Keep the digits and read them as UTC instead, * the table prints an all day event in UTC as well. * * @param {ICAL.Time} time Start of the occurrence * @returns {Date} The point in time to sort and print by */ function startOf(time) { if (time.isDate) { return new Date(Date.UTC(time.year, time.month - 1, time.day)); } return time.toJSDate(); } /** * The upcoming occurrences of a calendar. * * @param {ICAL.Component} calendar The parsed calendar * @param {Date} now Events must still be running at this date * @param {number} maxEvents Maximum number of events to return * @param {number} maxDays Maximum number of days into the future * @returns {{start: Date, allDay: boolean, name: string, url: string}[]} url is empty when the event has no URL */ function getUpcomingEvents(calendar, now, maxEvents, maxDays) { const end = new Date(now.getTime()); end.setDate(end.getDate() + maxDays); const events = []; // A running event stays listed until it is over, so the window starts at now // and the walk keeps everything that has not ended yet. for (const { event, startDate } of occurrencesBetween(calendar, now, end)) { events.push({ start: startOf(startDate), allDay: startDate.isDate, name: event.summary ?? "", url: eventUrl(event), }); } // We have occurrences from multiple events, so sort them // before applying the maximum event count. events.sort((a, b) => a.start - b.start); return events.slice(0, maxEvents); } document.addEventListener("DOMContentLoaded", () => { const max_days = 20; const max_items = 5; const now = new Date(); const table = document.getElementById("upcoming"); loadCalendar() .then(calendar => { getUpcomingEvents(calendar, now, max_items, max_days).forEach(event => { const row = document.createElement("tr"); const colBegin = document.createElement("td"); // The events take place in Berlin, so name their time in Berlin time // instead of in the time zone the visitor happens to be in. An all day // event has no time of day and carries its date in UTC, see startOf(). const whenFormat = event.allDay ? { timeZone: "UTC" } : { timeZone: "Europe/Berlin", hour: "2-digit", minute: "2-digit" }; const formattedStart = event.start.toLocaleString("de-DE", { weekday: "long", day: "2-digit", month: "2-digit", ...whenFormat, }); colBegin.innerText = event.allDay ? formattedStart : `${formattedStart} Uhr`; row.appendChild(colBegin); const colName = document.createElement("td"); if (event.url) { const a = document.createElement("a"); a.href = event.url; a.text = event.name; colName.appendChild(a); } else { colName.innerText = event.name; } row.appendChild(colName); table.appendChild(row); }); }) .catch(err => console.error("Fehler beim Laden der Termine:", err)); });