rollingthedice
rollingthedice

Reputation: 1125

Problem with datetime.time() comparison in a pre_save signal

I have those models:

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)

The main goal is:

When a CheckIn is created the code should check if it's within the given interval's time. I need to compare those two and return their difference in %H:%M:%S format.

What I've done so far:

Since i have different scenarios what to do with the given CheckIn before it gets saved, I am using a pre_save signal and the time comparison is a part of these scenarios. Here is a pseudo:

@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 is a timezone aware date, since i can print it's tzinfo which returns UTC instead of Europe/Sofia as of my TIME_ZONE in settings and I have no clue why.

So I manually tried to set it's tz by doing:

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.

The second thing i tried is to convert instance.interval.from_time to aware datetime and then compare both dates' 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

Now both dt1 and dt2 are aware but still no luck comparing them. Here's the complete code:

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

How can I get the proper time in dt1 and this comparison to work properly, even when both datetimes are aware with a proper timezone?

EDIT: I have USE_TZ = True in my settings.

Upvotes: 2

Views: 261

Answers (2)

Kevin Christopher Henry
Kevin Christopher Henry

Reputation: 49062

Django doesn't make any guarantees about what the timezone of your datetimes will be in Python code. That's because 1) timezones are mostly relevant for user interaction (which is why activate() affects forms and templates) and 2) Python comparison operations are timezone-aware, so the specific timezone doesn't affect the outcome.

Of course, one operation that is not timezone-aware is extracting the time from a datetime and comparing it to another time. Therefore you have to manually convert your datetime to the right timezone. You had the right idea, but replace() is the wrong way to do it. (Rather than merely converting to another timezone, it changes the actual moment in time.)

Instead, do this:

from django.utils.timezone import localtime

if localtime(instance.date_time).time() < instance.interval.from_time:
    pass

Upvotes: 1

Shakil
Shakil

Reputation: 4630

I did some googling and found that django intend to always save in UTC format in model but during retrive from db in converted into TIME_ZONE set by settings. link. So i suppose there is a possibility its in UTC format in pre-save instance. I might be wrong as i don't have concrete strong knowledge about it.

I think this comparison needs to done in real time. So its quite safe to use user TIME_ZONE now to compare with interval from_time. So my suggestion

import pytz
import datetime


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

and convert this line of code

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

to

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

Upvotes: 0

Related Questions