Django фильтр по диапазону дат, но без учета года

Я знаю, что если я хочу отфильтровать результаты по диапазону дат, я просто использую что-то вроде

Sample.objects.filter(birthday__range=(start_date, end_date))

Но как мне отфильтровать по диапазону дат, исключающему год?

Вы можете использовать функцию Extract на базе данных.

Например, этот простой запрос ниже возвращает элементы, у которых день рождения в 8-м месяце. Вы также можете извлечь день, месяц или год по своему усмотрению и отфильтровать по ним.

from django.db.models.functions import Extract


Sample.objects.annotate(birthday_month=Extract('birthday', 'month')).filter(birthday_month=8)

Дополнительная информация в документации.

https://docs.djangoproject.com/en/3.2/ref/models/database-functions/#extract

Вы можете использовать как фильтр, так и исключение. Фильтруйте, как вы сделали, а затем исключите год, например 2021

Sample.objects.filter(birthday__range=(start_date, end_date)).exclude(birthday_date__year=2021)

Мы можем работать с диапазоном дат с:

from datetime import timedelta
from django.db.models import Q

def filter_query(start_date, end_date):
    a = am, ad = start_date.month, start_date.day
    b = bm, bd = end_date.month, end_date.day
    end_date2 = end_date + timedelta(days=1)
    c = end_date2.month, end_date2.day
    if c == (2, 29) and a == (3, 1):
        return ~Q(birthday__month=2, birthday__day__gt=28)
    if a == c:
        return Q()
    if a <= b:
        if am == bm:
            return Q(birthday__month=am, birthday__day__range=(ad, bd))
        else:
            return (Q(birthday__month=am, birthday__day__gte=ad) |
                    Q(month__range=(am+1, bm-1)) |
                    Q(birthday__month=bm, birthday__day__lte=bd))
    else:
        return ~filter_query(end_date+timedelta(days=1), start_date-timedelta(days=1))

Это вернет объект Q, который затем можно использовать для фильтрации набора запросов, например:

Sample.objects.filter(filter_query(date(2020, 12, 15), date(2021, 1, 16)))

В основном существует пять случаев:

  • если end_date - 28 февраля, а дата начала - 1 марта, то мы исключаем 29 февраля.
  • .
  • если end_date плюс один день совпадает с датой начала, то нам не нужно фильтровать;
  • если start_date меньше чем end_date и месяцы совпадают, то фильтруем по этому месяцу, а по дням проверяем диапазон;
  • если start_date меньше чем end_date и месяцы не совпадают, тогда мы ищем диапазоны:
    • элементы с одинаковым месяцем start_date и с днем, большим, чем месяц start_date;
    • элементы с месяцем между следующим месяцем start_date и меньшим, чем предыдущий месяц end_date; и
    • элементы с тем же месяцем, что и end_date и днем меньше или равным дню end_date.
  • если конечная дата меньше начальной, то мы возвращаем отрицание end_date плюс один день и start_date минус один день.
Вернуться на верх