Skip to content

Instantly share code, notes, and snippets.

@ioggstream
Last active August 21, 2023 04:00
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ioggstream/bed73be9af647ae2fbe51291c39827a7 to your computer and use it in GitHub Desktop.
Save ioggstream/bed73be9af647ae2fbe51291c39827a7 to your computer and use it in GitHub Desktop.
Render a simple calendar with python.
from datetime import datetime, timedelta, timezone
from requests import get
import icalendar
from dateutil.rrule import *
import pytz
import locale
locale.setlocale(locale.LC_TIME, "it_IT")
import time
tz_rome = pytz.timezone("Europe/Rome")
url = "https://calendar.google.com/calendar/ical/rh620lf5mqo32dpv9k77r99ge8%40group.calendar.google.com/public/basic.ics"
def to_ts(dt):
return time.mktime(dt.timetuple())
def localize(dt):
if hasattr(dt, "astimezone"):
return dt.astimezone(tz_rome)
return dt
def date_fmt(dt):
return dt.strftime("%d %b %a %H:%M")
def parse_recurrences(recur_rule, start, exclusions):
""" Expand recurring events """
rules = rruleset()
first_rule = rrulestr(recur_rule, dtstart=start)
rules.rrule(first_rule)
if not isinstance(exclusions, list):
exclusions = [exclusions]
for xdate in exclusions:
try:
rules.exdate(xdate.dts[0].dt)
except AttributeError:
pass
now = datetime.now(timezone.utc)
this_year = now + timedelta(days=365)
dates = []
for rule in rules.between(now, this_year):
dates.append([to_ts(rule), date_fmt(rule), rule])
return dates
def get_calendar_from_url(url):
icalfile = get(url).content
gcal = icalendar.Calendar.from_ical(icalfile)
events = []
for component in gcal.walk():
if component.name == "VEVENT":
summary = component.get("summary")
description = component.get("description")
location = component.get("location")
startdt = localize(component.get("dtstart").dt)
enddt = localize(component.get("dtend").dt)
exdate = component.get("exdate")
if component.get("rrule"):
reoccur = component.get("rrule").to_ical().decode("utf-8")
for t, item, rule in parse_recurrences(reoccur, startdt, exdate):
s = f"{item} {summary} [{description}, {location}]"
events.append(
(
t,
s,
dict(
item=item,
start=rule,
summary=summary,
description=description,
location=location,
),
)
)
else:
inizia, finisce = date_fmt(startdt), date_fmt(enddt)
s = f"{inizia} - {finisce} {summary} [{description} {location}]"
events.append(
(
to_ts(startdt),
s,
dict(
start=startdt,
end=enddt,
summary=summary,
description=description,
location=location,
),
)
)
return [x[-1] for x in sorted(events)]
def render_events(sorted_events, output_file="calendar.md"):
"""
Create a markdown file with the rendered events,
that must be already sorted.
:return:
"""
today = None
this_month = None
from pathlib import Path
ofile = Path(output_file)
with ofile.open("w") as fh:
for e in sorted_events:
PAD = " " * 2
dt_start = localize(e["start"])
dt_end = localize(e["end"]) if "end" in e else dt_start
time_start = dt_start.strftime("%H:%M")
time_end = dt_end.strftime("%H:%M") if "end" in e else ""
summary = e["summary"]
location = e["location"]
description = e["description"]
event_day = (
e["start"].date() if isinstance(e["start"], datetime) else e["start"]
)
event_month = e["start"].strftime("%B").capitalize()
if event_month != this_month:
this_month = event_month
fh.write('\n<div style="page-break-after: always;"></div>\n')
fh.write(f"\n## {this_month} {event_day.year}\n")
if event_day != today:
today = event_day
f_day = today.strftime("%A %d %B").capitalize()
fh.write(f"\n\n**{f_day}**:\n\n")
if time_start == "00:00":
time_start = "(da definire)"
time_end = ""
fh.write(
"\n"
+ PAD
+ f" - {time_start}-{time_end} {summary} {description} {location}\n"
)
def run(cmd):
from shlex import split
from subprocess import check_output
check_output(split(cmd))
if __name__ == "__main__":
outfile = "calendar.md"
sorted_events = get_calendar_from_url(url)
render_events(sorted_events)
run(f"pandoc -f markdown -t html5 -o calendar.pdf {outfile}")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment