Проблема со сравнением datetime.time () в сигнале pre_save - PullRequest
0 голосов
/ 18 января 2019

У меня есть эти модели:

class Interval(models.Model):
    from_time = models.TimeField(auto_now_add=False)
    to_time = models.TimeField(auto_now_add=False)

class CheckIn(models.Model):
    date_time = models.DateTimeField(auto_now_add=True)
    interval = models.ForeignKey(Interval, on_delete=models.SET_NULL)

Основная цель:

Когда создается CheckIn, код должен проверять, находится ли он в заданном интервале времени. Мне нужно сравнить эти два и вернуть их разницу в формате %H:%M:%S.

Что я сделал до сих пор:

Поскольку у меня есть разные сценарии, что делать с данным CheckIn до его сохранения, я использую сигнал pre_save, и сравнение времени является частью этих сценариев. Вот псевдо:

@receiver(pre_save, sender=CheckIn)
def set_action_state(sender, instance, **kwargs):
    if something:
       ...
       if another:
          ...
       else:
          ...
    else:
        # instance.date_time.time() is 13:56:56.568860 (2 hours behind, it should be 15 instead of 13)
        # instance.interval.from_time is 15:07:40
        if instance.date_time.time() < instance.interval.from_time:
            # this returns True when it's meant to return False

instance.date_time - это дата с указанием часового пояса, поскольку я могу напечатать ее tzinfo, которая возвращает UTC вместо Europe/Sofia с моих TIME_ZONE в настройках, и я понятия не имею, почему.

Поэтому я вручную попытался установить tz, выполнив:

tz = pytz.timezone('Europe/Sofia')
dt1 = instance.date_time.replace(tzinfo=tz)
print(dt1.tzinfo) # which is the correct Europe/Sofia
print(dt1) # which is 13:56:56.568860, again 2 hours behind even with different timezone.

Второе, что я попробовал, - это преобразовать instance.interval.from_time в известную дату и затем сравнить обе даты 'time().

def time_to_datetime(date, time):
    return make_aware(datetime.datetime.combine(date, time))

dt2 = time_to_datetime(dt1.date(), instance.interval.from_time)
print(dt2.tzinfo) #this returns 'Europe/Sofia'
print(dt2) #this is 15:07:40

Теперь и dt1, и dt2 знают, но сравнивать их все же не повезло. Вот полный код:

tz = pytz.timezone('Europe/Sofia')
dt1 = instance.date_time.replace(tzinfo=tz)
dt2 = time_to_datetime(dt1.date(), instance.interval.from_time)
print(dt1.time() < dt2.time()) # this is True
print(dt1.tzinfo) # 'Europe/Sofia'
print(dt2.tzinfo) # 'Europe/Sofia'
print(dt1.time()) # 13:56:56.568860
print(dt1.time()) # 15:07:40

Как я могу получить правильное время в dt1, и это сравнение будет работать должным образом, даже если обе даты известны с правильным часовым поясом?

РЕДАКТИРОВАТЬ: У меня есть USE_TZ = True в моих настройках.

Ответы [ 2 ]

0 голосов
/ 19 января 2019

Django не дает никаких гарантий относительно того, какой часовой пояс будет указываться в коде Python.Это потому, что 1) часовые пояса в основном имеют отношение к взаимодействию с пользователем (именно поэтому activate() влияет на формы и шаблоны) и 2) операции сравнения Python учитывают часовой пояс, поэтому конкретный часовой пояс не влияет на результат.

Конечно, одна операция, которая не учитывает часовой пояс, - это извлечение времени из даты и времени и сравнение его с другим временем.Поэтому вы должны вручную преобразовать дату и время в правильный часовой пояс.У вас была правильная идея, но replace() - неправильный способ сделать это.(Вместо того, чтобы просто преобразовать в другой часовой пояс, он изменяет фактический момент времени.)

Вместо этого сделайте следующее:

from django.utils.timezone import localtime

if localtime(instance.date_time).time() < instance.interval.from_time:
    pass
0 голосов
/ 18 января 2019

Я немного погуглил и обнаружил, что django намеревается всегда сохранять в формате UTC в модели, но во время извлечения из db преобразуется в TIME_ZONE, установленный настройками. ссылка . Так что я полагаю, что есть возможность в формате UTC в сохраненном экземпляре. Я могу ошибаться, поскольку у меня нет конкретных сильных знаний об этом.

Я думаю, что это сравнение должно быть сделано в режиме реального времени. Так что теперь совершенно безопасно использовать пользователя TIME_ZONE для сравнения с интервалом from_time. Итак, мое предложение

import pytz
import datetime


tz = pytz.timezone('Europe/Sofia')
curtime = tz.localize(datetime.datetime.now())

и преобразовать эту строку кода

  if instance.date_time.time() < instance.interval.from_time:
            # this returns True when it's meant to return False

до

   if curtime < instance.interval.from_time:
            # I hope this return False now
...