termine.md now gets default data; also lots of bugfixes around dates and times

This commit is contained in:
eclipse 2025-02-17 10:04:01 +01:00
parent 372deef27e
commit b33d3aecbb
3 changed files with 99 additions and 24 deletions

View File

@ -0,0 +1,9 @@
title: Termine
author: Tobias Radloff
summary: Lesungen und Veranstaltungen
template: termine.noformat
lang: de
category: Termine
slug: termine
url: termine/
save_as: termine/index.html

View File

@ -8,6 +8,10 @@ SITESUBTITLE = "Schriftsteller"
SITEURL = "" SITEURL = ""
TIMEZONE = 'Europe/Berlin' TIMEZONE = 'Europe/Berlin'
DATE_FORMATS = {
'de': '%a, %d. %b %y',
'en': '%Y-%m-%d(%a)',
}
DEFAULT_LANG = 'de' DEFAULT_LANG = 'de'
THEME = "theme/" THEME = "theme/"
@ -22,7 +26,7 @@ PAGE_PATHS = ["pages"]
STATIC_PATHS = ["images", "favicon"] STATIC_PATHS = ["images", "favicon"]
DIRECT_TEMPLATES = ['index', 'tags'] DIRECT_TEMPLATES = ['index', 'tags']
IGNORE_FILES = ['**/.*', '__pycache__', 'favicon-from-svg.sh'] IGNORE_FILES = ['**/.*', '__pycache__', 'favicon-from-svg.sh', '*.metadata']
EXTRA_PATH_METADATA = { EXTRA_PATH_METADATA = {
'favicon/favicon.ico': {'path': 'favicon.ico'}, 'favicon/favicon.ico': {'path': 'favicon.ico'},

View File

@ -1,15 +1,25 @@
import caldav import caldav
import vobject import vobject
from datetime import datetime, date, time from datetime import datetime, date, time, timedelta
import yaml import yaml
import os
import locale
import threading
from contextlib import contextmanager
"""CalDAV server url"""
server_url = "https://***REMOVED*** server_url = "https://***REMOVED***
cal_user = "tobias" """CalDAV calendar url"""
cal_url = "https://***REMOVED*** cal_url = "https://***REMOVED***
"""CalDAV username"""
cal_user = "tobias"
"""Category to filter events for. Only events that belong to this category will be processed."""
cal_category = "Veranstaltung" cal_category = "Veranstaltung"
"""Name of the file the YAML data will be writte to."""
result_file = "../content/pages/termine.md" result_file = "../content/pages/termine.md"
now = datetime.now().astimezone() """List of icalendar properties that will be passed on to Pelican. At this time, all properties are used except those regarding alarms."""
cal_properties = [ # taken from ical specification cal_properties = [ # taken from ical specification
# descriptive # descriptive
"attach", "categories", "class", "comment", "description", "geo", "location", "percent-complete", "priority", "resources", "status", "summary", "attach", "categories", "class", "comment", "description", "geo", "location", "percent-complete", "priority", "resources", "status", "summary",
@ -26,10 +36,13 @@ cal_properties = [ # taken from ical specification
# change management # change management
"created", "dtstamp", "last-modified", "sequence" "created", "dtstamp", "last-modified", "sequence"
] ]
"""List of icalendar properties that the vobject passes as either datetime.datetime or datetime.date objects. To be able to compare any two objects, the script makes each one aware and/or converts into datetime.datetime if necessary. Default timezone is the system timezone; default time is 23:59:59."""
cal_prop_datetimes = ["dtend", "due", "dtstart", "duration", "dtstamp", "last-modified"] cal_prop_datetimes = ["dtend", "due", "dtstart", "duration", "dtstamp", "last-modified"]
"""Current time as an aware datetime.datetime object."""
now = datetime.now().astimezone()
"""Converts a datetime.datetime or datetime.date object into an aware datetime object.""" """Returns an aware datetime.datetime object based on the given datetime or date. Conversion happens only if necessary, i.e. if d is of type datetime.date or a naive datetime.datetime object."""
def fixDatetime(d, t=None, tz=None): def fixDatetime(d, t=None, tz=None):
if not tz: if not tz:
tz = now.tzinfo tz = now.tzinfo
@ -53,6 +66,30 @@ def fixDatetime(d, t=None, tz=None):
return datetime.combine(d, t) return datetime.combine(d, t)
"""Takes a list of events in vobject representation and converts each event into a dict of Python datatypes."""
def extractEventData(events):
e_tmp = []
# iterate over events
for e in events:
d = {}
# iterate over event properties
for k, v in e.contents.items():
# we're only interested in a subset of properties
if not k in cal_properties:
continue
# Usually, v is a list with a single item. We only need the item's value attribute
if len(v) == 1:
d[k] = v[0].value
# but sometimes the list has more than one item; this only ever happens for the property "categories"
# and in this case, each list item's value is itself a one item list (which is stupid but that's how vobject handles categories)
else:
d[k] = [v[i].value[0] for i in range(len(v))]
e_tmp.append(d)
return e_tmp
# get password # get password
with open(".caldav_pass", "r") as f: with open(".caldav_pass", "r") as f:
cal_pass = f.read().strip() cal_pass = f.read().strip()
@ -73,29 +110,17 @@ with caldav.DAVClient(url=server_url, username=cal_user, password=cal_pass) as d
# we only want events belonging to a specific category # we only want events belonging to a specific category
events = [e for e in vcal.getChildren() if "categories" in e.contents.keys() and cal_category in map(lambda x: x.value[0], e.contents["categories"])] events = [e for e in vcal.getChildren() if "categories" in e.contents.keys() and cal_category in map(lambda x: x.value[0], e.contents["categories"])]
# convert events into a structure suitable for Pelican # extract event data
e_tmp = [] events = extractEventData(events)
for e in events:
d = {}
for k, v in e.contents.items():
# only keep specific calendar properties
if not k in cal_properties:
continue
# v is usually a list with a single item
if len(v) == 1:
d[k] = v[0].value
# but sometimes there's more than one item (this only ever happens when k is "categories")
# and in this case, each list item's value is itself a one item list (which is stupid but that's how vobject handles categories)
else:
d[k] = [v[i].value[0] for i in range(len(v))]
e_tmp.append(d)
events = e_tmp
# fix dates and datetimes # fix dates and datetimes
for e in events: for e in events:
for k in e.keys(): for k in e.keys():
if k in cal_prop_datetimes: if k in cal_prop_datetimes:
# fix a CalDAV/vobject/icalendar (?) bug where a dtend value which is a date only (no time) automatically moves one day into the future
if k == "dtend" and type(e[k]) == date:
e[k] = e[k] - timedelta(days=1)
# fix all datetimes so they can be compared and sorted
e[k] = fixDatetime(e[k]) e[k] = fixDatetime(e[k])
# keep only future events # keep only future events
@ -104,9 +129,46 @@ events = [e for e in events if e["dtstart"] >= now]
# sort by start date # sort by start date
events.sort(key=lambda e: e["dtstart"]) events.sort(key=lambda e: e["dtstart"])
# read default metadata if present
default_metadata = None
if os.path.isfile(result_file + ".metadata"):
with open(result_file + ".metadata", "r") as f:
default_metadata = yaml.safe_load(f)
# set up a context manager in order to threadsafely format dates and times with the German locale
# source: https://stackoverflow.com/a/24070673
LOCALE_LOCK = threading.Lock()
@contextmanager
def setlocale(name):
with LOCALE_LOCK:
saved = locale.setlocale(locale.LC_TIME)
try:
yield locale.setlocale(locale.LC_TIME, name)
finally:
locale.setlocale(locale.LC_TIME, saved)
# temporary set locale to de_DE (category "time" only) and format start date as well as start time or end date
with setlocale('de_DE.UTF-8'):
for e in events:
# start date
e["startdate"] = e["dtstart"].strftime("%a, %d.%m.")
# start time (if not the default time)
if e["dtstart"].timetz() != time(23, 59, 59, tzinfo=now.tzinfo):
e["starttime"] = e["dtstart"].strftime("%H:%M Uhr")
# end date (if different from start date)
if e["dtstart"].date() < e["dtend"].date():
e["enddate"] = e["dtend"].date().strftime("%a, %d.%m.")
# add current date to default metadata while we're at it
if default_metadata:
default_metadata["date"] = now.strftime("%Y-%m-%d %H:%M")
# write data as YAML # write data as YAML
with open(result_file, 'w') as f: with open(result_file, 'w') as f:
f.write("---\n") f.write("---\n")
if default_metadata:
yaml.dump(default_metadata, f)
yaml.dump({"termine": events}, f) yaml.dump({"termine": events}, f)
f.write("---\n") f.write("---\n")
f.write("written at " + datetime.today().isoformat(" ")) f.write("written at " + datetime.today().isoformat(" "))