[Django]-Finding overlapping dates in a specific range in Django

7👍

Two intervals (s1, t1) and (s2, t2) do not overal, given t1 < s2, or t2 < s1. This thus means that two resources overlap given t1 ≥ s2 and t2 ≥ s1.

This thus means that you should implement a check like:

def isAvaible(self, endDate, initialDate):
    return not self.reunion_set.filter(end__gte=intialDate, start__lte=endDate).exists()

Note that you should not use .get(..) [Django-doc] here, since this will raise an error when there are no records, or when there are multiple records (two or more). You here can make use of .exists() [Django-doc] to find out if such record exists.

1👍

The answer of Willem will work for most of the cases. However, when we have an invalid interval i.e. start_date is after end_date, it won’t work.

I have made a generic function by referencing a similar question.

from django.db.models import Q


def create_query_for_finding_overlapping_intervals(start_date_column, end_date_column_name, start_dt, end_dt,
                                                   closed_interval=True):
    """
    Creates a query for finding intervals in the Django model which overlap the [start_date, end_date] closed interval.
    It also takes care of the invalid interval case when start date > end date for both stored ones and the input ones.

    :param start_date_column: name of start date column in the model
    :param end_date_column_name: name of end date column in the model
    :param start_dt: start date of the interval to be checked
    :param end_dt: end date of the interval to be checked
    :param closed_interval: closed interval = True means intervals are of the form [start, end],
     otherwise intervals are of the form [start, end). Where ")" means end-value is included and ")" end-value is not
    included.
    :return:
    """

    q_start_dt__gt = f'{start_date_column}__gt'
    q_start_dt__gte = f'{start_date_column}__gte'
    q_start_dt__lt = f'{start_date_column}__lt'
    q_start_dt__lte = f'{start_date_column}__lte'
    q_end_dt__gt = f'{end_date_column_name}__gt'
    q_end_dt__gte = f'{end_date_column_name}__gte'
    q_end_dt__lt = f'{end_date_column_name}__lt'
    q_end_dt__lte = f'{end_date_column_name}__lte'

    q_is_contained = Q(**{q_start_dt__gte: start_dt}) & Q(**{q_end_dt__lte: end_dt})
    q_contains = Q(**{q_start_dt__lte: start_dt}) & Q(**{q_end_dt__gte: end_dt})
    q_slides_before = Q(**{q_start_dt__lt: start_dt}) & Q(**{q_end_dt__lt: end_dt})
    q_slides_after = Q(**{q_start_dt__gt: start_dt}) & Q(**{q_end_dt__gt: end_dt})
    if closed_interval:
        q_slides_before = q_slides_before & Q(**{q_end_dt__gte: start_dt})
        q_slides_after = q_slides_after & Q(**{q_start_dt__lte: end_dt})
    else:
        q_slides_before = q_slides_before & Q(**{q_end_dt__gt: start_dt})
        q_slides_after = q_slides_after & Q(**{q_start_dt__lt: end_dt})

    return q_contains | q_is_contained | q_slides_before | q_slides_after

Usage:

def isAvaible(self, endDate, initialDate):
    query_obj = create_query_for_finding_overlapping_intervals('start', 'end', initialDate, endDate)
    return not self.reunion_set.filter(query_obj).exists()
👤damjad

1👍

@damjad solution worked for me but with a little modification at the last line which should be like this:

return q_contains | q_is_contained | q_slides_before | q_slides_after

Leave a comment