termine.md now gets default data; also lots of bugfixes around dates and times
This commit is contained in:
parent
372deef27e
commit
b33d3aecbb
9
content/pages/termine.md.metadata
Normal file
9
content/pages/termine.md.metadata
Normal 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
|
||||||
@ -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'},
|
||||||
|
|||||||
@ -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(" "))
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user