Django ORM: разница во времени в днях между 2 датами

#django #django-orm

Вопрос:

введите описание изображения здесь

У меня есть таблица базы данных, которая представляет расходы, связанные с данным продуктом.

Эти расходы, учитывая, что они ежедневные, имеют from_date (дату, в которую они начались) и to_date (дату, в которую они закончились). to_date может быть нулевым, так как эти расходы все еще могут продолжаться.

Учитывая 2 Python datetime s, start_date и end_date мне нужно указать в ORM общую сумму , потраченную за период my_product .

 >>> start_date
datetime.datetime(2021, 8, 20, 0, 0)

>>> end_date
datetime.datetime(2021, 9, 21, 0, 0)
 

В этом случае ожидаемый результат должен быть:

 (-104 * (days between 08/20 and 08/25))   (-113 * (days between 08/26 and 09/21)
 

Это то, что у меня есть до сих пор:

 (
my_product.income_streams
    .values("product")
    .filter(type=IncomeStream.Types.DAILY_EXPENSE)
    .filter(add_to_commission_basis=True)
    .annotate(period_expenses=Case(
        When(Q(from_date__lte=start_date) amp; Q(to_date__lte=end_date),
 then=ExpressionWrapper( start_date - F('to_date'), output_field=IntegerField()))
    ), # Other When cases...
)
) # Sum all period_expenses results and you've got the solution
 

И это то, что создает мне проблемы:

 then=ExpressionWrapper( start_date - F('to_date'), output_field=IntegerField())
 

Это выражение всегда возвращает 0 (пожалуйста, обратите внимание, что именно поэтому я даже не пытаюсь умножить на value : это был бы следующий шаг).

По-видимому start_date - F('to_date') , это не то же самое, что «дайте мне разницу в днях между этими 2 датами».

Вы бы завершили это на Python с timedelta помощью . Какой эквивалент в ORM?

Я пробовал с ExtractDay :

 then=ExpressionWrapper( ExtractDay(start_date - F('to_date'))
 

Но я понимаю: django.db.utils.OperationalError: user-defined function raised exception

А также попробовал с DurationField :

 then=ExpressionWrapper(start_date - F('to_date'), output_field=DurationField())
 

Но это также возвращает ноль: datetime.timedelta(0)

Ответ №1:

Приведение start_date в а DateTimeField решает проблему, а приведение разницы в а DurationField -это следующий шаг.

Так:

 Cast(Cast(start_date, output_field=DateTimeField()) - F('to_date'), output_field=DurationField())
 

Это будет хорошо работать на любой серверной части базы данных, но для того, чтобы получить разницу в днях, вам нужно обернуть ее в ExtractDay, что вызовет ValueError: Extract requires native DurationField database support. , если вы используете SQLite.

Если вы привязаны к SQLite и не можете использовать ExtractDay, вы можете использовать микросекунды и преобразовать их вручную в дни, разделив на 86400000000

 duration_in_microseconds=ExpressionWrapper(F('to_date') - (Cast(start_date, output_field=DateTimeField())), output_field=IntegerField())
 

Затем

 .annotate(duration_in_days=ExpressionWrapper(F('period_duration_microseconds') / 86400000000, output_field=DecimalField())