# Buggy code
def expand(vevent, href=""):
    """
    Constructs a list of start and end dates for all recurring instances of the
    event defined in vevent.

    It considers RRULE as well as RDATE and EXDATE properties. In case of
    unsupported recursion rules an UnsupportedRecurrence exception is thrown.
    If the timezone defined in vevent is not understood by icalendar,
    default_tz is used.

    :param vevent: vevent to be expanded
    :type vevent: icalendar.cal.Event
    :param href: the href of the vevent, used for more informative logging and
                 nothing else
    :type href: str
    :returns: list of start and end (date)times of the expanded event
    :rtype: list(tuple(datetime, datetime))
    """
    # we do this now and than never care about the "real" end time again
    if "DURATION" in vevent:
        duration = vevent["DURATION"].dt
    else:
        duration = vevent["DTEND"].dt - vevent["DTSTART"].dt

    events_tz = getattr(vevent["DTSTART"].dt, "tzinfo", None)
    allday = not isinstance(vevent["DTSTART"].dt, datetime)

    def sanitize_datetime(date):
        if allday and isinstance(date, datetime):
            date = date.date()
        if events_tz is not None:
            date = events_tz.localize(date)
        return date

    rrule_param = vevent.get("RRULE")
    if rrule_param is not None:
        vevent = sanitize_rrule(vevent)

        # dst causes problem while expanding the rrule, therefore we transform
        # everything to naive datetime objects and transform back after
        # expanding
        # See https://github.com/dateutil/dateutil/issues/102
        dtstart = vevent["DTSTART"].dt
        if events_tz:
            dtstart = dtstart.replace(tzinfo=None)

        rrule = dateutil.rrule.rrulestr(rrule_param.to_ical().decode(), dtstart=dtstart)

        if rrule._until is None:
            # rrule really doesn't like to calculate all recurrences until
            # eternity, so we only do it until 2037, because a) I'm not sure
            # if python can deal with larger datetime values yet and b) pytz
            # doesn't know any larger transition times
            rrule._until = datetime(2037, 12, 31)
        elif getattr(rrule._until, "tzinfo", None):
            rrule._until = rrule._until.astimezone(events_tz).replace(tzinfo=None)

        rrule = map(sanitize_datetime, rrule)

        logger.debug(
            "calculating recurrence dates for {0}, this might take some time.".format(
                href
            )
        )

        # RRULE and RDATE may specify the same date twice, it is recommended by
        # the RFC to consider this as only one instance
        dtstartl = set(rrule)
        if not dtstartl:
            raise UnsupportedRecurrence()
    else:
        dtstartl = {vevent["DTSTART"].dt}

    def get_dates(vevent, key):
        dates = vevent.get(key)
        if dates is None:
            return
        if not isinstance(dates, list):
            dates = [dates]

        dates = (leaf.dt for tree in dates for leaf in tree.dts)
        dates = localize_strip_tz(dates, events_tz)
        return map(sanitize_datetime, dates)

    # include explicitly specified recursion dates
    dtstartl.update(get_dates(vevent, "RDATE") or ())

    # remove excluded dates
    for date in get_dates(vevent, "EXDATE") or ():
        try:
            dtstartl.remove(date)
        except KeyError:
            logger.warning(
                "In event {}, excluded instance starting at {} not found, "
                "event might be invalid.".format(href, date)
            )

    dtstartend = [(start, start + duration) for start in dtstartl]
    # not necessary, but I prefer deterministic output
    dtstartend.sort()
    return dtstartend


# Traceback (AttributeError)
Traceback (most recent call last):
File "/usr/local/Cellar/khal/0.9.5/libexec/lib/python3.6/site-packages/khal/ui/__init__.py", line 1296, in start_pane
loop.run()
File "/usr/local/Cellar/khal/0.9.5/libexec/lib/python3.6/site-packages/urwid/main_loop.py", line 278, in run
self._run()
File "/usr/local/Cellar/khal/0.9.5/libexec/lib/python3.6/site-packages/urwid/main_loop.py", line 376, in _run
self.event_loop.run()
File "/usr/local/Cellar/khal/0.9.5/libexec/lib/python3.6/site-packages/urwid/main_loop.py", line 682, in run
self._loop()
File "/usr/local/Cellar/khal/0.9.5/libexec/lib/python3.6/site-packages/urwid/main_loop.py", line 719, in _loop
self._watch_files[fd]()
File "/usr/local/Cellar/khal/0.9.5/libexec/lib/python3.6/site-packages/urwid/raw_display.py", line 393, in <lambda>
event_loop, callback, self.get_available_raw_input())
File "/usr/local/Cellar/khal/0.9.5/libexec/lib/python3.6/site-packages/urwid/raw_display.py", line 493, in parse_input
callback(processed, processed_codes)
File "/usr/local/Cellar/khal/0.9.5/libexec/lib/python3.6/site-packages/urwid/main_loop.py", line 403, in _update
self.process_input(keys)
File "/usr/local/Cellar/khal/0.9.5/libexec/lib/python3.6/site-packages/urwid/main_loop.py", line 509, in process_input
something_handled |= bool(self.unhandled_input(k))
File "/usr/local/Cellar/khal/0.9.5/libexec/lib/python3.6/site-packages/urwid/main_loop.py", line 555, in unhandled_input
return self._unhandled_input(input)
File "/usr/local/Cellar/khal/0.9.5/libexec/lib/python3.6/site-packages/khal/ui/base.py", line 162, in on_key_press
self.backtrack()
File "/usr/local/Cellar/khal/0.9.5/libexec/lib/python3.6/site-packages/khal/ui/base.py", line 147, in backtrack
cb(data)
File "/usr/local/Cellar/khal/0.9.5/libexec/lib/python3.6/site-packages/khal/ui/__init__.py", line 1095, in cleanup
event.delete_instance(rec_id)
File "/usr/local/Cellar/khal/0.9.5/libexec/lib/python3.6/site-packages/khal/khalendar/event.py", line 602, in delete_instance
delete_instance(self._vevents['PROTO'], instance)
File "/usr/local/Cellar/khal/0.9.5/libexec/lib/python3.6/site-packages/khal/khalendar/utils.py", line 327, in delete_instance
_add_exdate(vevent, instance)
File "/usr/local/Cellar/khal/0.9.5/libexec/lib/python3.6/site-packages/khal/khalendar/utils.py", line 292, in _add_exdate
exdates.append(dates_from_exdate(vddlist))
File "/usr/local/Cellar/khal/0.9.5/libexec/lib/python3.6/site-packages/khal/khalendar/utils.py", line 282, in dates_from_exdate
return [dts.dt for dts in vevent['EXDATE'].dts]
AttributeError: 'list' object has no attribute 'dts'

# Fixed code
def expand(vevent, href=""):
    """
    Constructs a list of start and end dates for all recurring instances of the
    event defined in vevent.

    It considers RRULE as well as RDATE and EXDATE properties. In case of
    unsupported recursion rules an UnsupportedRecurrence exception is thrown.
    If the timezone defined in vevent is not understood by icalendar,
    default_tz is used.

    :param vevent: vevent to be expanded
    :type vevent: icalendar.cal.Event
    :param href: the href of the vevent, used for more informative logging and
                 nothing else
    :type href: str
    :returns: list of start and end (date)times of the expanded event
    :rtype: list(tuple(datetime, datetime))
    """
    # we do this now and than never care about the "real" end time again
    if "DURATION" in vevent:
        duration = vevent["DURATION"].dt
    else:
        duration = vevent["DTEND"].dt - vevent["DTSTART"].dt

    events_tz = getattr(vevent["DTSTART"].dt, "tzinfo", None)
    allday = not isinstance(vevent["DTSTART"].dt, datetime)

    def sanitize_datetime(date):
        if allday and isinstance(date, datetime):
            date = date.date()
        if events_tz is not None:
            date = events_tz.localize(date)
        return date

    rrule_param = vevent.get("RRULE")
    if rrule_param is not None:
        vevent = sanitize_rrule(vevent)

        # dst causes problem while expanding the rrule, therefore we transform
        # everything to naive datetime objects and transform back after
        # expanding
        # See https://github.com/dateutil/dateutil/issues/102
        dtstart = vevent["DTSTART"].dt
        if events_tz:
            dtstart = dtstart.replace(tzinfo=None)

        rrule = dateutil.rrule.rrulestr(rrule_param.to_ical().decode(), dtstart=dtstart)

        if rrule._until is None:
            # rrule really doesn't like to calculate all recurrences until
            # eternity, so we only do it until 2037, because a) I'm not sure
            # if python can deal with larger datetime values yet and b) pytz
            # doesn't know any larger transition times
            rrule._until = datetime(2037, 12, 31)
        elif getattr(rrule._until, "tzinfo", None):
            rrule._until = rrule._until.astimezone(events_tz).replace(tzinfo=None)

        rrule = map(sanitize_datetime, rrule)

        logger.debug(
            "calculating recurrence dates for {0}, this might take some time.".format(
                href
            )
        )

        # RRULE and RDATE may specify the same date twice, it is recommended by
        # the RFC to consider this as only one instance
        dtstartl = set(rrule)
        if not dtstartl:
            raise UnsupportedRecurrence()
    else:
        dtstartl = {vevent["DTSTART"].dt}

    def get_dates(vevent, key):
        # TODO replace with get_all_properties
        dates = vevent.get(key)
        if dates is None:
            return
        if not isinstance(dates, list):
            dates = [dates]

        dates = (leaf.dt for tree in dates for leaf in tree.dts)
        dates = localize_strip_tz(dates, events_tz)
        return map(sanitize_datetime, dates)

    # include explicitly specified recursion dates
    dtstartl.update(get_dates(vevent, "RDATE") or ())

    # remove excluded dates
    for date in get_dates(vevent, "EXDATE") or ():
        try:
            dtstartl.remove(date)
        except KeyError:
            logger.warning(
                "In event {}, excluded instance starting at {} not found, "
                "event might be invalid.".format(href, date)
            )

    dtstartend = [(start, start + duration) for start in dtstartl]
    # not necessary, but I prefer deterministic output
    dtstartend.sort()
    return dtstartend