您可以同时使用__date lookup 和TruncDate function:
from django.db.models import DateField, ExpressionWrapper, F
from django.db.models.functions import TruncDate
Test.obejcts.filter(
date2__date__gt=ExpressionWrapper(
TruncDate(F('date1')) + datetime.timedelta(days=365),
output_field=DateField(),
),
)
如果你真正需要的是date1 = 2019-05-14、date2 > 2020-05-14。那么这种方法并不总是正确的,因为闰年有 366 天。可以同时使用Trunc 和Extract 函数来解决此问题。不同的方法是可能的......例如:
from django.db.models import DateField, ExpressionWrapper, F
from django.db.models.functions import TruncDate, ExtractDay
date_field = DateField()
YEAR = timedelta(days=365)
LEAP_YEAR = timedelta(days=366)
shifted_date1 = ExpressionWrapper(
TruncDate(F('date1')) + YEAR,
output_field=date_field,
)
leap_shifted_date1 = ExpressionWrapper(
TruncDate(F('date1')) + LEAP_YEAR,
output_field=date_field,
)
qs = Test.objects.filter(
(
# It's ok to add 365 days if...
Q(date2__date__gt=shifted_date1)
&
(
# If day of month after 365 days is the same...
Q(date1__day=ExtractDay(shifted_date1))
|
# Or it's 29-th of February
Q(
date1__month=2,
date1__day=29,
)
)
)
|
Q(
# Use 366 days for other cases
date2__date__gt=leap_shifted_date1,
)
)
附:如果您有 USE_TZ = True 并在特定时区执行查询(例如,之前使用 timezone.activate(...)
执行查询集),那么在添加timedelta 之前执行TruncDate 很重要,因为在每个在不同日期执行切换到“夏令时”的国家/地区执行TruncDate(F('date1')+timedelta(...)) 可能会产生不正确的结果年。例如:
- 一些国家/地区在 2019 年将
2019-03-31 切换到 DST 时间,并将在 2020 年切换到 2020-03-29。
-
2019-03-30 23:30 上的当地时间尚未使用 DST。
- 加上 366 天(因为明年是闰年)会得到
2020-03-30 23:30 "non-DST",所以在“标准化”之后这个日期时间会变成
2020-03-31 00:30 "DST"
- 在添加 timedelta 之前使用
TruncDate 可以解决问题,因为 TruncDate casts value to date。
额外信息:某些国家/地区将在固定日期改用 DST,例如每年 2 月 1 日,其他人可能会切换到“3 月的最后一个星期日”,这可能是每年不同的日期。
import pytz
import datetime
kyiv.localize(datetime.datetime(2011, 3, 28, 0, 1)) - kyiv.localize(datetime.datetime(2010, 3, 28, 0, 1))
# `datetime.timedelta(364, 82800)` is less than 365 days
附言“闰秒年”的最后几秒(2016-12-31 23:59:60.999)也可能受到 TruncDate/timedelta-shift 排序的影响,但“幸运的是”大多数数据库不支持闰秒,而 python 的 @987654346 @
也没有这个功能