This is a more thorough approach on fetching and parsing recurring events. It has been tested on Google and Yahoo iCalendar, so it might not work on others. As further testing and feedpack is required to test it's reliability, the parsing of reucrring events is switched off by default. Setting 'use_recurring_events' to 'True' activates parsing of recurring events.
91 lines
3.4 KiB
Python
91 lines
3.4 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 = False ## Attention: experimental feature!
|
|
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)
|
|
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')))
|
|
|
|
|
|
"""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
|