簡體   English   中英

日期時間對象上的 Django F 表達式

[英]Django F expression on datetime objects

我的模型是:

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

我可以使用以下查詢找出date2大於date1對象:

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

我想找到date2date1一年的所有對象。
如何根據date1date2之間的差異找出對象?

一般解決方案:

您可以annotate日期差異,然后根據timedelta(days=365) (與@Anonymous 在他的評論中建議的非常接近):

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


PostgreSQL 特定解決方案:

如果您使用的是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))

您可以同時使用__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 天。 這個問題可以結合使用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,
    )
)

PS 如果您有USE_TZ = True並在特定時區執行查詢(例如在執行查詢集之前使用timezone.activate(...) ),那么添加timedelta之前執行TruncDate很重要,因為執行TruncDate(F('date1')+timedelta(...))在每年在不同日期執行切換到“夏令時”的國家/地區可能會給出不正確的結果。 例如:

  • 一些國家在 2019 年在2019-03-31切換到 DST 時間, 2019-03-31在 2020 年切換到2020-03-29
  • 當地時間2019-03-30 23:30還沒有使用夏令時。
  • 添加 366 天(因為明年是閏年)將得到2020-03-30 23:30 "non-DST" ,所以在“標准化”之后,這個日期時間將變成2020-03-31 00:30 "DST"
  • 在添加TruncDate之前使用TruncDate解決了這個問題,因為TruncDate將值轉換為 date

額外信息:一些國家/地區在固定日期(例如每年 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 最后幾秒 ( 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