static void combine_vavailability(struct calquery_filter *calfilter)
{
    struct vavailability_array *vavail = &calfilter->vavail;
    struct calquery_filter availfilter;
    struct query_range {
	struct icalperiodtype per;
	struct query_range *next;
    } *ranges, *range, *prev, *next;
    unsigned i, j;

    memset(&availfilter, 0, sizeof(struct calquery_filter));

    /* Sort VAVAILBILITY periods by priority and start time */
    qsort(vavail->vav, vavail->len,
	  sizeof(struct vavailability), compare_vavail);

    /* Maintain a linked list of remaining time ranges
     * to be filled in by lower priority VAV components
     * starting with the time range in the freebusy query.
     * Ranges are removed, clipped, or split as they get filled.
     * Quit when no ranges or VAVAILABILITY components remain.
     */
    ranges = xmalloc(sizeof(struct query_range));
    ranges->per.start = calfilter->start;
    ranges->per.end = calfilter->end;
    ranges->next = NULL;

    for (i = 0; i < vavail->len; i++) {
	struct vavailability *vav = &vavail->vav[i];
	icalcomponent *comp;

	comp = icalcomponent_get_first_component(vav->ical,
						 ICAL_VAVAILABILITY_COMPONENT);

	for (range = ranges, prev = NULL; range; prev = range, range = next) {
	    struct icalperiodtype period;
	    icaltime_span span;

	    next = range->next;

	    if (icaltime_compare(vav->per.end, range->per.start) <= 0 ||
		icaltime_compare(vav->per.start, range->per.end) >= 0) {
		/* Skip VAVAILABILITY outside our range */
		continue;
	    }

	    /* Set filter range (maximum start time and minimum end time)
	       and adjust current range as necessary */
	    if (icaltime_compare(vav->per.start, range->per.start) <= 0) {
		/* VAV starts before range - filter using range start */
		availfilter.start = range->per.start;

		if (icaltime_compare(vav->per.end, range->per.end) >= 0) {
		    /* VAV ends after range - filter using range end */
		    availfilter.end = range->per.end;

		    /* Filling entire range - remove it */
		    if (prev) prev->next = next;
		    else ranges = NULL;

		    free(range);
		    range = NULL;
		}
		else {
		    /* VAV ends before range - filter using VAV end */
		    availfilter.end = vav->per.end;

		    /* Filling front part - adjust start to back remainder */
		    range->per.start = vav->per.end;
		}
	    }
	    else {
		/* VAV starts after range - filter using VAV start */
		availfilter.start = vav->per.start;

		if (icaltime_compare(vav->per.end, range->per.end) >= 0) {
		    /* VAV ends after range - filter using range end */
		    availfilter.end = range->per.end;
		}
		else {
		    /* VAV ends before range - filter using VAV end */
		    availfilter.end = vav->per.end;

		    /* Splitting range - insert new range for back remainder */
		    struct query_range *newr =
			xmalloc(sizeof(struct query_range));
		    newr->per.start = vav->per.end;
		    newr->per.end = range->per.end;
		    newr->next = next;
		    range->next = newr;
		}

		/* Adjust end to front remainder */
		range->per.end = vav->per.start;
	    }

	    /* Expand available time occurrences */
	    expand_occurrences(comp, ICAL_XAVAILABLE_COMPONENT, &availfilter);

	    /* Calculate unavailable periods and add to busytime */
	    period.start = availfilter.start;
	    for (j = 0; j < availfilter.freebusy.len; j++) {
		struct freebusy *fb = &availfilter.freebusy.fb[j];

		/* Ignore overridden instances */
		if (fb->type == ICAL_FBTYPE_NONE) continue;

		period.end = fb->per.start;
		if (icaltime_compare(period.end, period.start) > 0) {
		    span = icaltime_span_new(period.start, period.end, 1);
		    add_freebusy_comp(comp, &span, calfilter);
		}
		period.start = fb->per.end;
	    }
	    period.end = availfilter.end;
	    if (icaltime_compare(period.end, period.start) > 0) {
		span = icaltime_span_new(period.start, period.end, 1);
		add_freebusy_comp(comp, &span, calfilter);
	    }
	}

	/* Done with this ical component */
	icalcomponent_free(vav->ical);
    }

    /* Cleanup the vavailability array */
    free(vavail->vav);

    /* Cleanup the availability array */
    if (availfilter.freebusy.fb) free(availfilter.freebusy.fb);

    /* Remove any unfilled ranges */
    for (; ranges; ranges = next) {
	next = ranges->next;
	free(ranges);
    }
}