From 15de43a2b5f297750cde6b906687cbc11d37dd7f Mon Sep 17 00:00:00 2001 From: Tobias Feigel Date: Sat, 6 Sep 2025 12:27:56 +0200 Subject: [PATCH] Enhances event handling with timezone support Adds timezone handling to the event parsing and formatting logic. This allows events to be displayed in the user's local timezone, improving the user experience. Also restructures the event display on the "Termine" page to include a sidebar for filtering by year and month. --- site/collections/termine.php | 154 ++++++++++++++++------ site/snippets/home-termine.php | 128 ++++++++++++------ site/snippets/termine.php | 231 +++++++++++++++++++-------------- 3 files changed, 339 insertions(+), 174 deletions(-) diff --git a/site/collections/termine.php b/site/collections/termine.php index 1b60e3f..6e7e3e3 100644 --- a/site/collections/termine.php +++ b/site/collections/termine.php @@ -1,66 +1,142 @@ -format('d.m.Y H:i') : $date; + } else { + $dt = DateTime::createFromFormat('Ymd', $date); + + return $dt ? $dt->format('d.m.Y') : $date; + } +} + +function format_ics_date_with_timezone($date, $timezone = null) +{ + // Unterstützt sowohl ganztägige als auch Zeitangaben + if (strpos($date, 'T') !== false) { + $dt = null; + + // Prüfe ob es eine UTC-Zeit ist (endet mit Z) + if (substr($date, -1) === 'Z') { + // UTC-Zeit: 20250405T130000Z + $utc_date = substr($date, 0, -1); // Entferne das Z + $dt = DateTime::createFromFormat('Ymd\THis', $utc_date, new DateTimeZone('UTC')); + } elseif ($timezone) { + // Zeitzone angegeben: 20250405T130000 mit TZID + try { + $dt = new DateTime($date, new DateTimeZone($timezone)); + } catch (Exception $e) { + // Fallback auf lokale Zeit $dt = DateTime::createFromFormat('Ymd\THis', substr($date, 0, 15)); - - return $dt ? $dt->format('d.m.Y H:i') : $date; + } } else { - $dt = DateTime::createFromFormat('Ymd', $date); - - return $dt ? $dt->format('d.m.Y') : $date; + // Lokale Zeit ohne Zeitzone + $dt = DateTime::createFromFormat('Ymd\THis', substr($date, 0, 15)); } + + if ($dt) { + // ISO 8601 Format für JavaScript + $iso_date = $dt->format('Y-m-d\TH:i:sP'); // P = Zeitzone + $display_date = $dt->format('d.m.Y H:i'); + + return [ + 'iso' => $iso_date, + 'display' => $display_date, + 'has_time' => true, + ]; + } + + return [ + 'iso' => $date, + 'display' => $date, + 'has_time' => true, + ]; + } else { + $dt = DateTime::createFromFormat('Ymd', $date); + + if ($dt) { + $iso_date = $dt->format('Y-m-d'); + $display_date = $dt->format('d.m.Y'); + + return [ + 'iso' => $iso_date, + 'display' => $display_date, + 'has_time' => false, + ]; + } + + return [ + 'iso' => $date, + 'display' => $date, + 'has_time' => false, + ]; + } } // ICS-Datei laden $ics = @file_get_contents(CAL_URL); -if (! $ics) { - echo '
Kalender konnte nicht geladen werden.
'; +if (!$ics) { + echo '
Kalender konnte nicht geladen werden.
'; - return; + return; } // Termine parsen function parse_ics($ics) { - $lines = explode("\n", $ics); - $events = []; - $event = []; - $inEvent = false; - foreach ($lines as $line) { - $line = trim($line); - if ($line === 'BEGIN:VEVENT') { - $inEvent = true; - $event = []; - } elseif ($line === 'END:VEVENT') { - $inEvent = false; - $events[] = $event; - } elseif ($inEvent) { - // Property kann Parameter enthalten, z.B. DTSTART;TZID=Europe/Berlin:20240701T19000000 - $parts = explode(':', $line, 2); - if (count($parts) === 2) { - $key = $parts[0]; - $val = $parts[1]; - // Nur den eigentlichen Property-Namen extrahieren - $key = strtoupper(preg_replace('/;.+$/', '', $key)); - $event[$key] = $val; - } - } - } + $lines = explode("\n", $ics); + $events = []; + $event = []; + $inEvent = false; + foreach ($lines as $line) { + $line = trim($line); + if ($line === 'BEGIN:VEVENT') { + $inEvent = true; + $event = []; + } elseif ($line === 'END:VEVENT') { + $inEvent = false; + $events[] = $event; + } elseif ($inEvent) { + // Property kann Parameter enthalten, z.B. DTSTART;TZID=Europe/Berlin:20240701T19000000 + $parts = explode(':', $line, 2); + if (count($parts) === 2) { + $key = $parts[0]; + $val = $parts[1]; - return $events; + // Zeitzoneninformation extrahieren + $timezone = null; + if (preg_match('/TZID=([^:;]+)/', $key, $matches)) { + $timezone = $matches[1]; + } + + // Nur den eigentlichen Property-Namen extrahieren + $cleanKey = strtoupper(preg_replace('/;.+$/', '', $key)); + $event[$cleanKey] = $val; + + // Zeitzoneninformation separat speichern + if ($timezone && in_array($cleanKey, ['DTSTART', 'DTEND'])) { + $event[$cleanKey . '_TZID'] = $timezone; + } + } + } + } + + return $events; } $events = parse_ics($ics); return function () use ($events) { - return $events; + return $events; }; + diff --git a/site/snippets/home-termine.php b/site/snippets/home-termine.php index 5cd7c96..e734ec6 100644 --- a/site/snippets/home-termine.php +++ b/site/snippets/home-termine.php @@ -1,34 +1,34 @@ format('Ymd'); +// Aktuelles Datum +$today = new DateTime()->format('Ymd'); - // Nur zukünftige Termine anzeigen - $future_events = array_filter($events, function ($event) use ($today) { - return isset($event['DTSTART']) && $event['DTSTART'] >= $today; - }); +// Nur zukünftige Termine anzeigen +$future_events = array_filter($events, function ($event) use ($today) { + return isset($event['DTSTART']) && $event['DTSTART'] >= $today; +}); - // Termine nach DTSTART in aufsteigender Reihenfolge sortieren - usort($future_events, function ($a, $b) { - return $a['DTSTART'] <=> $b['DTSTART']; - }); +// Termine nach DTSTART in aufsteigender Reihenfolge sortieren +usort($future_events, function ($a, $b) { + return $a['DTSTART'] <=> $b['DTSTART']; +}); - // --- Deutsche Monatsnamen --- - $de_months = [ - '01' => 'Jan', - '02' => 'Feb', - '03' => 'Mrz', - '04' => 'Apr', - '05' => 'Mai', - '06' => 'Jun', - '07' => 'Jul', - '08' => 'Aug', - '09' => 'Sep', - '10' => 'Okt', - '11' => 'Nov', - '12' => 'Dez', - ]; +// --- Deutsche Monatsnamen --- +$de_months = [ + '01' => 'Jan', + '02' => 'Feb', + '03' => 'Mrz', + '04' => 'Apr', + '05' => 'Mai', + '06' => 'Jun', + '07' => 'Jul', + '08' => 'Aug', + '09' => 'Sep', + '10' => 'Okt', + '11' => 'Nov', + '12' => 'Dez', +]; ?>
@@ -43,16 +43,19 @@
+ $desc = $event['DESCRIPTION'] ?? ''; + $timezone = $event['DTSTART_TZID'] ?? null; + $date_info = format_ics_date_with_timezone($start, $timezone); + $date = $date_info['display']; + $time = $date_info['has_time'] ? substr($date, 11) : 'ganztägig'; + $day = substr($date, 0, 2); + $month = substr($date, 3, 2); + $iso_date = $date_info['iso']; + ?>
@@ -66,7 +69,9 @@
-
+
@@ -81,8 +86,16 @@
-

-

ab Uhr

+

+

+ + ab Uhr + + ganztägig + +

@@ -169,6 +182,45 @@ // Initialer Aufruf updateArrowState(); + // Zeitzonenkonvertierung für lokale Zeiten + function convertTimesToLocal() { + const timeElements = document.querySelectorAll('.local-time'); + timeElements.forEach(element => { + const isoDate = element.getAttribute('data-iso-date'); + if (isoDate) { + try { + // Erstelle ein Date-Objekt aus der ISO-Date + // JavaScript erkennt automatisch UTC-Zeiten (mit Z oder +00:00) + const date = new Date(isoDate); + + // Prüfe ob das Datum gültig ist + if (isNaN(date.getTime())) { + console.warn('Ungültiges Datum:', isoDate); + return; + } + + // Formatiere die Zeit in der lokalen Zeitzone des Browsers + const localTime = date.toLocaleTimeString('de-DE', { + hour: '2-digit', + minute: '2-digit', + hour12: false + }); + + // Aktualisiere den Text + element.textContent = localTime; + + // Debug-Information (kann später entfernt werden) + console.log('Konvertiert:', isoDate, '->', localTime, 'in Zeitzone:', Intl.DateTimeFormat().resolvedOptions().timeZone); + } catch (error) { + console.warn('Fehler bei der Zeitzonenkonvertierung:', error, 'für Datum:', isoDate); + } + } + }); + } + + // Zeitzonenkonvertierung beim Laden der Seite + convertTimesToLocal(); + // Responsives Verhalten - bei Fenstergröße-Änderung window.addEventListener('resize', () => { // Anzahl der Karten je nach Bildschirmgröße anpassen diff --git a/site/snippets/termine.php b/site/snippets/termine.php index 7274264..f5e9cb3 100644 --- a/site/snippets/termine.php +++ b/site/snippets/termine.php @@ -4,77 +4,77 @@ $events = collection('termine'); // Nur Events mit DTSTART berücksichtigen $events = array_filter($events, function ($event) { - return isset($event['DTSTART']) && ! empty($event['DTSTART']); + return isset($event['DTSTART']) && !empty($event['DTSTART']); }); // Nach Datum sortieren usort($events, function ($a, $b) { - return strcmp($a['DTSTART'], $b['DTSTART']); + return strcmp($a['DTSTART'], $b['DTSTART']); }); // Aktuelles Datum -$today = (new DateTime())->format('Ymd'); +$today = new DateTime()->format('Ymd'); // Nur zukünftige Termine anzeigen $future_events = array_filter($events, function ($event) use ($today) { - return isset($event['DTSTART']) && $event['DTSTART'] >= $today; + return isset($event['DTSTART']) && $event['DTSTART'] >= $today; }); // --- Deutsche Monatsnamen --- $de_months = [ - '01' => 'Januar', - '02' => 'Februar', - '03' => 'März', - '04' => 'April', - '05' => 'Mai', - '06' => 'Juni', - '07' => 'Juli', - '08' => 'August', - '09' => 'September', - '10' => 'Oktober', - '11' => 'November', - '12' => 'Dezember', + '01' => 'Januar', + '02' => 'Februar', + '03' => 'März', + '04' => 'April', + '05' => 'Mai', + '06' => 'Juni', + '07' => 'Juli', + '08' => 'August', + '09' => 'September', + '10' => 'Oktober', + '11' => 'November', + '12' => 'Dezember', ]; // --- Gruppierung aller Termine nach Jahr und Monat für die Sidebar --- function group_events_by_year_month($events) { - $grouped = []; - foreach ($events as $event) { - if ( ! isset($event['DTSTART'])) { - continue; - } - $date = $event['DTSTART']; - $year = substr($date, 0, 4); - $month = substr($date, 4, 2); - $grouped[$year][$month][] = $event; - } - krsort($grouped); // Jahre absteigend - foreach ($grouped as &$months) { - krsort($months); // Monate absteigend + $grouped = []; + foreach ($events as $event) { + if (!isset($event['DTSTART'])) { + continue; } + $date = $event['DTSTART']; + $year = substr($date, 0, 4); + $month = substr($date, 4, 2); + $grouped[$year][$month][] = $event; + } + krsort($grouped); // Jahre absteigend + foreach ($grouped as &$months) { + krsort($months); // Monate absteigend + } - return $grouped; + return $grouped; } $all_events_grouped = group_events_by_year_month($events); // --- Filter aus URL --- -$filter_jahr = $_GET['jahr'] ?? null; +$filter_jahr = $_GET['jahr'] ?? null; $filter_monat = $_GET['monat'] ?? null; if ($filter_jahr && $filter_monat) { - $filtered_events = array_filter($events, function ($event) use ($filter_jahr, $filter_monat) { - $date = $event['DTSTART'] ?? ''; + $filtered_events = array_filter($events, function ($event) use ($filter_jahr, $filter_monat) { + $date = $event['DTSTART'] ?? ''; - return substr($date, 0, 4) === $filter_jahr && substr($date, 4, 2) === $filter_monat; - }); + return substr($date, 0, 4) === $filter_jahr && substr($date, 4, 2) === $filter_monat; + }); } elseif ($filter_jahr) { - $filtered_events = array_filter($events, function ($event) use ($filter_jahr) { - $date = $event['DTSTART'] ?? ''; + $filtered_events = array_filter($events, function ($event) use ($filter_jahr) { + $date = $event['DTSTART'] ?? ''; - return substr($date, 0, 4) === $filter_jahr; - }); + return substr($date, 0, 4) === $filter_jahr; + }); } else { - $filtered_events = $future_events; + $filtered_events = $future_events; } ?> @@ -85,54 +85,43 @@ if ($filter_jahr && $filter_monat) {

Termine nach Jahr/Monat

\ No newline at end of file