-
-
Save jandye/9147854f5fe70bf67170c5c7163144a0 to your computer and use it in GitHub Desktop.
Render a simple calendar with python.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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