Skip to content

Instantly share code, notes, and snippets.

@meskarune
Last active December 2, 2023 01:48
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save meskarune/63600e64df56a607efa211b9a87fb443 to your computer and use it in GitHub Desktop.
Save meskarune/63600e64df56a607efa211b9a87fb443 to your computer and use it in GitHub Desktop.
parsing ical file with python icalendar

Archwomen.ics file

BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VEVENT
CREATED:20170220T182458Z
LAST-MODIFIED:20170220T182458Z
DTSTAMP:20170220T182458Z
UID:a79d1ffd-f62b-4c14-98b7-22eaa494a7fd
SUMMARY:Arch Linux Women Monthly Project Meeting
RRULE:FREQ=MONTHLY;BYDAY=2SA
DTSTART;VALUE=DATE:20170211T160000Z
DTEND;VALUE=DATE:20170211T180000Z
LOCATION:#archlinux-women @ irc.freenode.net
DESCRIPTION:https://archwomen.org/wiki/meetings
URL:https://archwomen.org/wiki/meetings:start
TRANSP:TRANSPARENT
END:VEVENT
BEGIN:VEVENT
CREATED:20120514T060851Z
LAST-MODIFIED:20120514T061031Z
DTSTAMP:20120514T061031Z
UID:e0639f9c-5ab0-4001-9da9-3003611b6e94
SUMMARY:Bug Day
RRULE:FREQ=WEEKLY
DTSTART;VALUE=DATE:20120821T000000Z
DTEND;VALUE=DATE:20120822T000000Z
LOCATION:#archlinux-bugs @ Freenode IRC
DESCRIPTION:http://bugs.archlinux.org/
TRANSP:TRANSPARENT
END:VEVENT
BEGIN:VEVENT
CREATED:20170220T182458Z
LAST-MODIFIED:20170220T182458Z
DTSTAMP:20170220T182458Z
UID:0c9acbbb-f82e-444d-b941-50c3a9953d86
SUMMARY:Test unique date
DTSTART;VALUE=DATE:20170303T110000Z
DTEND;VALUE=DATE:20170303T113000Z
LOCATION:My computer
DESCRIPTION:A test for a one time event
TRANSP:OPAQUE
END:VEVENT
END:VCALENDAR`

Python code

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
""" Parse ical file and report all events in the next 2 months """

from datetime import datetime, timedelta, timezone
import icalendar
from dateutil.rrule import *


def parse_recurrences(recur_rule, start, exclusions):
    """ Find all reoccuring 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=60)
    dates = []
    for rule in rules.between(now, this_year):
        dates.append(rule.strftime("%D %H:%M UTC "))
    return dates

icalfile = open('Archwomen.ics', 'rb')
gcal = icalendar.Calendar.from_ical(icalfile.read())
for component in gcal.walk():
    if component.name == "VEVENT":
        summary = component.get('summary')
        description = component.get('description')
        location = component.get('location')
        startdt = component.get('dtstart').dt
        enddt = component.get('dtend').dt
        exdate = component.get('exdate')
        if component.get('rrule'):
            reoccur = component.get('rrule').to_ical().decode('utf-8')
            for item in parse_recurrences(reoccur, startdt, exdate):
                print("{0} {1}: {2} - {3}\n".format(item, summary, description, location))
        else:
            print("{0}-{1} {2}: {3} - {4}\n".format(startdt.strftime("%D %H:%M UTC"), enddt.strftime("%D %H:%M UTC"), summary, description, location))
icalfile.close()

Output

03/11/17 16:00 UTC  Arch Linux Women Monthly Project Meeting: https://archwomen.org/wiki/meetings - #archlinux-women @irc.freenode.net

04/08/17 16:00 UTC  Arch Linux Women Monthly Project Meeting: https://archwomen.org/wiki/meetings - #archlinux-women @irc.freenode.net

02/28/17 00:00 UTC  Bug Day: http://bugs.archlinux.org/ - #archlinux-bugs @ Freenode IRC

03/07/17 00:00 UTC  Bug Day: http://bugs.archlinux.org/ - #archlinux-bugs @ Freenode IRC

03/14/17 00:00 UTC  Bug Day: http://bugs.archlinux.org/ - #archlinux-bugs @ Freenode IRC

03/21/17 00:00 UTC  Bug Day: http://bugs.archlinux.org/ - #archlinux-bugs @ Freenode IRC

03/28/17 00:00 UTC  Bug Day: http://bugs.archlinux.org/ - #archlinux-bugs @ Freenode IRC

04/04/17 00:00 UTC  Bug Day: http://bugs.archlinux.org/ - #archlinux-bugs @ Freenode IRC

04/11/17 00:00 UTC  Bug Day: http://bugs.archlinux.org/ - #archlinux-bugs @ Freenode IRC

04/18/17 00:00 UTC  Bug Day: http://bugs.archlinux.org/ - #archlinux-bugs @ Freenode IRC

03/03/17 11:00 UTC-03/03/17 11:30 UTC Test unique date: A test for a one time event - My computer

Todo

  • order dates soonist to now to farthest
  • some sort of error handling?
@fantamiracle
Copy link

One correction to the code based on my tests:

    if not isinstance(exclusions, list):
        exclusions = [exclusions]
        for xdate in exclusions:
            try:
                rules.exdate(xdate.dts[0].dt)
            except AttributeError:
                pass

This needs to be replaced by the following, in order for all xdates to work.

    if exclusions:
        for xdt in exclusions.dts:
            try:
                rules.exdate(xdt.dt)
            except AttributeError:
                pass

@may-cat
Copy link

may-cat commented Oct 24, 2017

Thank you a lot!
This snippet saved me from going crazy with ical!

By the way, I had to change

from datetime import datetime, timedelta, timezone

to

from datetime import *
from django.utils import timezone

@scls19fr
Copy link

scls19fr commented Nov 27, 2017

For filtering and sorting you can simply do with functional approach of Python:

components = gcal.walk()
components = filter(lambda c: c.name=='VEVENT', components)
components = sorted(components, key=lambda c: c.get('dtstart').dt, reverse=False)

You can also use LINQ like libraries.

With py-linq

from py_linq import Enumerable
components = gcal.walk()
components = Enumerable(components)
components = components.where(lambda c: c.name=='VEVENT').order_by(lambda c: c.get('dtstart').dt)

With asq

from asq.initiators import query
components = gcal.walk()
components = query(components).where(lambda c: c.name=='VEVENT').order_by(lambda c: c.get('dtstart').dt)

Edit 1: py-linq and asq have order_by_descending method

Edit2: order dates soonist to now to farthest using filter/sorted

now = datetime.now(timezone.utc)
# now = datetime(2017, 2, 10, tzinfo=timezone.utc)
components = filter(lambda c: c.name=='VEVENT', components)
components = filter(lambda c: c.get('dtstart').dt - now > timedelta(0), components)  # filter out past events
components = sorted(components, key=lambda c: c.get('dtstart').dt - now, reverse=False)  # order dates soonist to now to farthest

@jeinarsson
Copy link

@meskarune Thanks for this. I hacked a bit for my own purposes, should it be useful for anyone https://gist.github.com/jeinarsson/989329deb6906cae49f6e9f979c46ae7

@nuhkoca
Copy link

nuhkoca commented Dec 30, 2021

Can somebody help with this question? I am really really stuck on it. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment