Search code examples
djangopython-3.xdatetimedjango-modelsdjango-timezone

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.


Solution

  • 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