Inkycal/modules/inkycal_icalendar.py
Ace 7011914cc5
Using recurring events by default
As the tests for the recurring events have passed, the recurring events feature can now be used by default. Event if it fails, the try/except handling won't let the script crash in case something goes wrong while parsing recurring events.
2019-12-17 16:17:29 +01:00

95 lines
3.5 KiB
Python

#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
iCalendar (parsing) module for Inky-Calendar Project
Copyright by aceisace
"""
from __future__ import print_function
from configuration import *
from settings import ical_urls
import arrow
from ics import Calendar
use_recurring_events = True
print_events = False
style = 'DD MMM YY HH:mm'
if use_recurring_events == True:
from dateutil.rrule import rrulestr, rruleset
import re
def fetch_events():
"""Set timelines for filtering upcoming events"""
timezone = get_tz()
now = arrow.now(tz=timezone)
beginning_of_month = now.replace(days= - now.day +1)
near_future = now.replace(days= 30)
further_future = now.replace(days=40)
"""Parse the iCalendars from the urls, fixing some known errors with ics"""
calendars = [Calendar(fix_ical(url)) for url in ical_urls]
"""Filter any upcoming events from all iCalendars and add them to a list"""
upcoming_events = [events for ical in calendars for events in ical.events
if beginning_of_month <= events.end <= further_future or
beginning_of_month <= events.begin <= near_future]
"""Try to parse recurring events. This is clearly experimental! """
if use_recurring_events == True:
for ical in calendars:
for events in ical.events:
event_str = str(events)
if re.search('RRULE:(.+?)\n', event_str):
if events.all_day and events.duration.days > 1:
events.end = events.end.replace(days=-2)
else:
events.begin = events.begin.to(timezone)
events.end = events.end.to(timezone)
try:
rule = re.search('RRULE:(.+?)\n', event_str).group(0)[:-2]
if re.search('UNTIL=(.+?);', rule) and not re.search('UNTIL=(.+?)Z;', rule):
rule = re.sub('UNTIL=(.+?);', 'UNTIL='+re.search('UNTIL=(.+?);', rule).group(0)[6:-1]+'T000000Z;', rule)
dates = rrulestr(rule, dtstart= events.begin.datetime).between(after= now.datetime, before = further_future.datetime)
if dates:
duration = events.duration
for date in dates:
cc = events.clone()
cc.end = arrow.get(date+duration)
cc.begin = arrow.get(date)
upcoming_events.append(cc)
#print("Added '{}' with new start at {}".format(cc.name, cc.begin.format('DD MMM YY')))
except Exception as e:
print('Something went wrong while parsing recurring events')
pass
"""Sort events according to their beginning date"""
def sort_dates(event):
return event.begin
upcoming_events.sort(key=sort_dates)
"""Multiday events are displayed incorrectly; fix that"""
for events in upcoming_events:
if events.all_day and events.duration.days > 1:
events.end = events.end.replace(days=-2)
if not events.all_day:
events.begin = events.begin.to(timezone)
events.end = events.end.to(timezone)
""" The list upcoming_events should not be modified. If you need the data from
this one, copy the list or the contents to another one."""
#print(upcoming_events) # Print all events. Might look a bit messy
"""Print upcoming events in a more appealing way"""
if print_events == True and upcoming_events:
line_width = max(len(i.name) for i in upcoming_events)
for events in upcoming_events:
print('{0} {1} | {2} | {3} | All day ='.format(events.name,
' '* (line_width - len(events.name)), events.begin.format(style),
events.end.format(style)), events.all_day)
return upcoming_events