[英]Django F expression on datetime objects
My model is:我的模型是:
class Test():
date1 = models.DateTimeField()
date2 = models.DateTimeField()
I can find out objects whose date2
is greater than date1
, using the following query:我可以使用以下查询找出date2
大于date1
对象:
Test.obejcts.filter(date2__gt=F('date1'))
I would like to find all the objects whose date2
is greater than date1
by one year.我想找到date2
比date1
一年的所有对象。
How can I find out objects based on difference between date1
and date2
?如何根据date1
和date2
之间的差异找出对象?
General Solution:一般解决方案:
You can annotate
the date difference and then check this against the timedelta(days=365)
(pretty close to what @Anonymous suggests in his comment):您可以annotate
日期差异,然后根据timedelta(days=365)
(与@Anonymous 在他的评论中建议的非常接近):
Test.objects.annotate(
duration=F('date2') - F('date1')
).filter(duration__gt=timedelta(days=365))
If you are using PostgreSQL
, there is another option derived from this answer :如果您使用的是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))
You can use __date
lookup and TruncDate
function together:您可以同时使用__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(),
),
)
If what you really need is something like date1 = 2019-05-14
, date2 > 2020-05-14
.如果您真正需要的是date1 = 2019-05-14
, date2 > 2020-05-14
。 Then this approach is not always correct because leap year have 366 days.那么这种方法并不总是正确的,因为闰年有 366 天。 This issue can be solved using Trunc
and Extract
functions together.这个问题可以结合使用Trunc
和Extract
函数来解决。 Different approaches are possible... For example:不同的方法是可能的......例如:
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,
)
)
PS If you have USE_TZ = True
and performing queries in specific timezone (eg use timezone.activate(...)
before executing querysets), then it's important to do TruncDate
before adding timedelta
, because doing TruncDate(F('date1')+timedelta(...))
may give incorrect results in countries where switch to "Daylight saving time" is performed on different dates each year. PS 如果您有USE_TZ = True
并在特定时区执行查询(例如在执行查询集之前使用timezone.activate(...)
),那么在添加timedelta
之前执行TruncDate
很重要,因为执行TruncDate(F('date1')+timedelta(...))
在每年在不同日期执行切换到“夏令时”的国家/地区可能会给出不正确的结果。 For example:例如:
2019-03-31
in year 2019 and will switch 2020-03-29
in year 2020.一些国家在 2019 年在2019-03-31
切换到 DST 时间, 2019-03-31
在 2020 年切换到2020-03-29
。2019-03-30 23:30
is not using DST yet.当地时间2019-03-30 23:30
还没有使用夏令时。2020-03-30 23:30 "non-DST"
, so after "normalization" this datetime will become 2020-03-31 00:30 "DST"
添加 366 天(因为明年是闰年)将得到2020-03-30 23:30 "non-DST"
,所以在“标准化”之后,这个日期时间将变成2020-03-31 00:30 "DST"
TruncDate
before adding timedelta solves the issue, because TruncDate
casts value to date .在添加TruncDate
之前使用TruncDate
解决了这个问题,因为TruncDate
将值转换为 date 。Extra info: some countries are switching to DST on a fixed dates eg on 1-st of February each year, others might be switching "on last Sunday of March" which might be a different date each year.额外信息:一些国家/地区在固定日期(例如每年 2 月 1 日)切换到 DST,其他国家/地区可能会在“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
PPS last seconds of " leap second year" ( 2016-12-31 23:59:60.999
) might have been affected by ordering of TruncDate/timedelta-shift too, but "fortunately" most databases do not support leap seconds, and python's datetime.datetime
also lacks this feature “闰秒年”的 PPS 最后几秒 ( 2016-12-31 23:59:60.999
) 也可能受到 TruncDate/timedelta-shift 排序的影响,但“幸运的是”大多数数据库不支持闰秒,而 python 的datetime.datetime
也没有这个功能
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.