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

Posted

技术标签:

【中文标题】日期时间对象上的 Django F 表达式【英文标题】:Django F expression on datetime objects 【发布时间】:2017-10-08 23:07:56 【问题描述】:

我的模型是:

class Test():
   date1 = models.DateTimeField()
   date2 = models.DateTimeField()

我可以使用以下查询找出date2 大于date1 的对象:

Test.obejcts.filter(date2__gt=F('date1'))

我想找出所有date2date1 大一年的对象。 如何根据date1date2 之间的差异找出对象?

【问题讨论】:

没有方便的安装尝试,但是Test.objects.annotate(next_year=F('date1') + timedelta(days=365)).filter(next_year__gt=F('date2')) 呢?或者在注释表达式中减去两个日期。 【参考方案1】:

一般解决方案:

您可以annotate 日期差,然后对照timedelta(days=365) 进行检查(非常接近@Anonymous 在他的评论中建议的内容):

Test.objects.annotate(
    duration=F('date2') - F('date1')
).filter(duration__gt=timedelta(days=365))


PostgreSQL 特定解决方案:

如果您使用的是PostgreSQL,还有另一个源自this answer 的选项:

from django.db.models import F, Func

Test.objects.annotate(
    duration = Func(F('date2'), F('date1'), function='age')
).filter(duration__gt=timedelta(days=365))

【讨论】:

如果我使用 Test.objects.annotate(duration=F('date2') - F('date1')),我会在查询集中得到一些十进制值。这些价值观是什么? @MahabuburRahamanMelon 您是否有机会使用 mysql? ***.com/questions/7483363/… 是的,我正在使用 MySQL。这个解决方案有问题吗? 尝试使用annotate(duration=F('end_time') - F('start_time')).filter(duration__gt=timedelta(0,299)),但收到错误:TypeError: expected string or bytes-like object @sytech 似乎是您代码中其他地方的问题,与此线程无关。也许与您的迁移有关:***.com/questions/40353649/…【参考方案2】:

您可以同时使用__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-14date2 > 2020-05-14。那么这种方法并不总是正确的,因为闰年​​有 366 天。可以同时使用TruncExtract 函数来解决此问题。不同的方法是可能的......例如:

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-292019-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 @ 也没有这个功能

【讨论】:

以上是关于日期时间对象上的 Django F 表达式的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Django F 表达式中将日期和时间字段合并到 DateTime 字段中

Django 中 UpdateView 上的日期格式更改

模型上的 Django 中的日期时间?

Django注释添加间隔日期

网站上的日期排序不正确?

Django:按日期范围过滤对象