#!/usr/bin/env python3 import datetime as dt import os import re from urllib.parse import parse_qsl, urlencode, urlsplit import requests from bs4 import BeautifulSoup from fpdf import FPDF MONTHS = ( "Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember", ) WEEKDAYS = ("Mo", "Di", "Mi", "Do", "Fr", "Sa", "So") DATE_PATTERNS = ( (re.compile(r"(? 3 else "" raise RuntimeError( f"CKalender-Daten für {label} sind unvollständig " f"({len(missing)} Tage fehlen: {preview}{suffix})." ) def calendar_url_for_period(url, start, end, kind): parts = urlsplit(url) overridden = {"calendar_kind", "from_date", "until_date"} query = [ item for item in parse_qsl(parts.query, keep_blank_values=True) if item[0] not in overridden ] query += [ ("calendar_kind", kind), ("from_date", start.isoformat()), ("until_date", end.isoformat()), ] return parts._replace(query=urlencode(query)).geturl() def fetch_calendar_cells(url, start, end, kind): response = requests.get(calendar_url_for_period(url, start, end, kind), timeout=30) response.raise_for_status() return calendar_cells(response.text) def load_calendar_cells(url, today): months = target_months(today) cells = fetch_calendar_cells(url, months[0], month_end(months[1]), "year") for month in months: if not missing_days(cells, month): continue end = month_end(month) fetched = fetch_calendar_cells(url, month, end, "month") cells.update({ day: cell for day, cell in fetched.items() if month <= day <= end }) return cells def add_table_header(pdf): pdf.set_font("helvetica", "B", 10) for index, (width, label, align) in enumerate(TABLE_COLUMNS): pdf.cell(width, 5, label, 1, int(index == len(TABLE_COLUMNS) - 1), align) def add_footer(pdf, now): pdf.set_font("helvetica", "", 8) pdf.cell(0, 1, " ", 0, 1) pdf.cell(0, 0.1, " ", 1, 1) pdf.cell(0, 1, " ", 0, 1) pdf.cell(153, 3, "gemeindeleitung@nak-reinickendorf.de", 0, 0) pdf.cell(30, 3, pdf_safe("Klixstraße 17, 13403 Berlin"), 0, 1) pdf.set_font("helvetica", "I", 8) pdf.cell(153, 3, f"Berlin, {now:%d.%m.%Y %H:%M}", 0, 0) pdf.set_font("helvetica", "", 8) pdf.cell(30, 3, "nak-reinickendorf.de", 0, 1) def add_month(pdf, cells, month, today=None): for day in month_days(month): pdf.set_fill_color(130) pdf.cell(0, 0.3, " ", 0, 1, fill=True) pdf.set_fill_color(255 if day.day % 2 else 220) pdf.set_font("helvetica", "B" if day.weekday() >= 5 else "", 8) if day.weekday() == 6: pdf.set_fill_color(170, 210, 255) if day == today: pdf.set_fill_color(255, 255, 0) pdf.cell(20, 4, day.strftime("%d.%m.%Y"), 0, 0, "C", fill=True) pdf.cell(15, 4, WEEKDAYS[day.weekday()], 0, 0, "C", fill=True) events = list(event_rows(cells[day])) if day in cells else [] if not events: pdf.cell(0, 4, " ", 0, 1, fill=True) continue for index, (time_text, title, place) in enumerate(events): if index: pdf.cell(35, 4, " ", 0, 0, fill=True) pdf.cell(20, 4, pdf_safe(time_text), 0, 0, "C", fill=True) pdf.cell(75, 4, pdf_safe(title), 0, 0, fill=True) pdf.cell(60, 4, pdf_safe(place), 0, 1, "C", fill=True) def add_month_section(pdf, cells, month, now, today=None, planning_note=False): name = MONTHS[month.month - 1] pdf.set_font("helvetica", "B", 16) pdf.cell(160, 7, pdf_safe(f"Terminübersicht: {name.upper()} {month.year}"), 0, 1) logo = os.path.join(os.path.dirname(__file__), "nak_logo.png") if not os.path.isfile(logo): raise RuntimeError(f"Logo fehlt: {logo}") pdf.image(logo, 188, pdf.get_y() - 7.5, 11) pdf.set_font("helvetica", "", 10) subtitle = ( f"Die Termine für {name} befinden sich noch in Planung und können sich ändern." if planning_note else "der Gemeinde Berlin-Reinickendorf" ) pdf.cell(160, 5, pdf_safe(subtitle), 0, 1) pdf.cell(0, 2, " ", 0, 1) add_table_header(pdf) add_month(pdf, cells, month, today) add_footer(pdf, now) def create_pdf(cells, output_path, now=None): now = now or dt.datetime.now() today = now.date() months = target_months(today) for month in months: require_month(cells, month) pdf = FPDF("P", "mm", "A4") pdf.set_auto_page_break(auto=True, margin=15) pdf.add_page() for index, month in enumerate(months): if index and pdf.page_no() == 1: pdf.add_page() elif index: pdf.cell(0, 6, " ", 0, 1) add_month_section( pdf, cells, month, now, today=today if index == 0 else None, planning_note=index == 1, ) output_dir = os.path.dirname(output_path) if output_dir: os.makedirs(output_dir, exist_ok=True) pdf.output(output_path) def main(): url = os.environ.get("CKAL_CALENDAR_URL") if not url: raise RuntimeError("CKAL_CALENDAR_URL ist nicht gesetzt.") now = dt.datetime.now() cells = load_calendar_cells(url, now.date()) create_pdf(cells, os.environ.get("PDF_OUTPUT", "/data/pdf/terminuebersicht.pdf"), now) if __name__ == "__main__": main()