forked from cccb-website-team/www
108 lines
3.1 KiB
Python
Executable file
108 lines
3.1 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
|
|
import sys
|
|
import html
|
|
import logging
|
|
import locale
|
|
from datetime import datetime, timedelta, timezone, date
|
|
from dateutil.rrule import rruleset, rrulestr
|
|
|
|
import icalendar
|
|
|
|
|
|
def _to_aware(dt):
|
|
"""Normalize a date or datetime to a timezone-aware UTC datetime."""
|
|
if isinstance(dt, date) and not isinstance(dt, datetime):
|
|
return datetime(dt.year, dt.month, dt.day, tzinfo=timezone.utc)
|
|
if dt.tzinfo is None:
|
|
return dt.replace(tzinfo=timezone.utc)
|
|
return dt.astimezone(timezone.utc)
|
|
|
|
|
|
def vevent_to_event(event, rrstart=None):
|
|
if rrstart is None:
|
|
begin = _to_aware(event["DTSTART"].dt)
|
|
else:
|
|
begin = _to_aware(rrstart)
|
|
|
|
return {
|
|
"name": str(event.get("SUMMARY", "")),
|
|
"url": str(event.get("URL", "")),
|
|
"begin": begin
|
|
}
|
|
|
|
|
|
def parse_single_event(event, start, end):
|
|
logging.info(f"Processing single event {event.get('SUMMARY', '')}")
|
|
dtstart = _to_aware(event["DTSTART"].dt)
|
|
if dtstart >= start and dtstart < end:
|
|
return vevent_to_event(event)
|
|
|
|
|
|
def parse_recurring_event(event, start, end):
|
|
logging.info(f"Processing recurring event {event.get('SUMMARY', '')}")
|
|
dtstart = _to_aware(event["DTSTART"].dt)
|
|
rs = rruleset()
|
|
rs.rrule(rrulestr(event["RRULE"].to_ical().decode("utf-8"), dtstart=dtstart))
|
|
if "EXDATE" in event.keys():
|
|
exdates = event["EXDATE"]
|
|
if not isinstance(exdates, list):
|
|
exdates = [exdates]
|
|
for exdate_list in exdates:
|
|
for dt in exdate_list.dts:
|
|
rs.exdate(_to_aware(dt.dt))
|
|
|
|
events = []
|
|
for dt in rs.between(start, end, inc=True):
|
|
events.append(vevent_to_event(event, dt))
|
|
|
|
return events
|
|
|
|
|
|
def find_events(icsfilestr, start, end, num):
|
|
with open(icsfilestr, "r") as icsfile:
|
|
cal = icalendar.Calendar.from_ical(icsfile.read())
|
|
|
|
events = []
|
|
for event in cal.subcomponents:
|
|
if event.name == "VEVENT":
|
|
if "RRULE" in event.keys():
|
|
events.extend(parse_recurring_event(event, start, end))
|
|
else:
|
|
ev = parse_single_event(event, start, end)
|
|
if ev is not None:
|
|
events.append(ev)
|
|
|
|
events = sorted(events, key=lambda k: k["begin"])
|
|
events = events[0:num]
|
|
|
|
return events
|
|
|
|
|
|
def format_events(events):
|
|
print("<table class=\"table table-condensed\">")
|
|
for event in events:
|
|
name = html.escape(event['name'])
|
|
url = html.escape(event['url'])
|
|
print(
|
|
"<tr>"
|
|
f"<td>{event['begin'].strftime('%A, %d.%m um %H:%M Uhr')}</td>"
|
|
f"<td><a href=\"{url}\">{name}</a></td>"
|
|
"</tr>"
|
|
)
|
|
print("</table><!--/.table .table-condensed-->")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) < 4:
|
|
print(f"Usage: {sys.argv[0]} calendar max_days max_items")
|
|
sys.exit(1)
|
|
|
|
locale.setlocale(locale.LC_TIME, "de_DE.UTF-8")
|
|
calendar = sys.argv[1]
|
|
max_days = int(sys.argv[2])
|
|
max_items = int(sys.argv[3])
|
|
|
|
now = datetime.now(timezone.utc)
|
|
events = find_events(calendar, now, now + timedelta(days=max_days), max_items)
|
|
format_events(events)
|