简体   繁体   English

日期时间对象上的 Django F 表达式

[英]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.我想找到date2date1一年的所有对象。
How can I find out objects based on difference between date1 and date2 ?如何根据date1date2之间的差异找出对象?

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))


PostgreSQL Specific Solution: PostgreSQL 特定解决方案:

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.这个问题可以结合使用TruncExtract函数来解决。 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:例如:

  • Some country switched to DST time on 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
  • Local time on 2019-03-30 23:30 is not using DST yet.当地时间2019-03-30 23:30还没有使用夏令时。
  • Adding 366 days (because next year is a leap year) to it will give 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"
  • Using 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM