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
минус один день.