我的模型是:
class Test():
date1 = models.DateTimeField()
date2 = models.DateTimeField()
使用以下查询语句,我可以找到 date2
大于 date1
的对象:
Test.objects.filter(date2__gt=F('date1'))
我想找到所有date2
比date1
晚一年的物品。
基于date1
和date2
之间的差异,我该如何查找物品?
我的模型是:
class Test():
date1 = models.DateTimeField()
date2 = models.DateTimeField()
使用以下查询语句,我可以找到 date2
大于 date1
的对象:
Test.objects.filter(date2__gt=F('date1'))
我想找到所有date2
比date1
晚一年的物品。
基于date1
和date2
之间的差异,我该如何查找物品?
通用解决方案:
您可以使用annotate
注释日期差异,然后将其与timedelta(days=365)
进行比较(非常接近@Anonymous在评论中建议的方法):
Test.objects.annotate(
duration=F('date2') - F('date1')
).filter(duration__gt=timedelta(days=365))
如果您正在使用PostgreSQL
,则有另一种选项,源自这个答案:
from django.db.models import F, Func
Test.objects.annotate(
duration = Func(F('date2'), F('date1'), function='age')
).filter(duration__gt=timedelta(days=365))
annotate(duration=F('end_time') - F('start_time')).filter(duration__gt=timedelta(0,299))
,但是遇到了一个错误:TypeError: 期望为字符串或类似字节的对象
。 - sytech__date
查询条件 和 TruncDate
函数:
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(...))
可能会导致错误结果。例如:
2020-03-30 23:30 "non-DST"
,因此在"normalization"后,这个日期时间将变成 2020-03-31 00:30 "DST"
TruncDate
解决了这个问题,因为TruncDate
将值强制转换为日期。额外信息:一些国家在固定日期(例��每年的2月1日)切换到夏令时,而其他国家可能会“在三月的最后一个星期日”切换夏令时,这可能是每年不同的日期。
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的datetime.datetime
也缺少此功能。
Test.objects.annotate(next_year=F('date1') + timedelta(days=365)).filter(next_year__gt=F('date2'))
怎么样?或者在注释表达式中对两个日期进行减法运算。 - Anonymous