def coalesce_dates(dates):
    """
    Coalesces all date pairs into combined date pairs that makes it easy to find free time gaps.

    >>> from date_collapse import coalesce_dates
    >>> dates = [(1,4),(2,8),(12,16),(16,21)]
    >>> cdates = coalesce_dates(dates)
    >>> print(cdates)
    [(1, 8), (12, 21)]
    >>> dates = [(1,4),(2,8),(8,10),(12,16),(16,21),(21,31)]
    >>> cdates = coalesce_dates(dates)
    >>> print(cdates)
    [(1, 10), (12, 31)]

    """

    parsed_dates = []
    for date in dates:
        parsed_dates.extend([(date[0], 1),(date[1], -1)])
    parsed_dates.sort(key = lambda d: d[0])

    count = 0
    coalesced = []
    current_block = [None, None]
    for date in parsed_dates:
        if count == 0:
            if not coalesced or (coalesced[-1][1] != date[0]):
                current_block = [date[0], None]
            else:
                coalesced.pop()
        count += date[1]
        if count == 0:
            current_block[1] = date[0]
            coalesced.append((current_block[0], current_block[1]))

    return coalesced