2

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.

rollingthedice
  • 1,095
  • 8
  • 17

2 Answers2

1

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
Kevin Christopher Henry
  • 46,175
  • 7
  • 116
  • 102
  • That was exactly what i needed. I knew there was a difference between user's `datetime` and python's `datetime` but I thought converting them to equal timezone would convert their date and time in python as well. Brilliant! – rollingthedice Jan 19 '19 at 10:36
0

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
Shakil
  • 4,520
  • 3
  • 26
  • 36
  • Unfortunately this throws `ValueError: Not naive datetime (tzinfo is already set)` – rollingthedice Jan 18 '19 at 16:35
  • I am really sorry for that. As you already set your timeZone can you please try with timezone.now() [link](https://docs.djangoproject.com/en/2.1/topics/i18n/timezones/#naive-and-aware-datetime-objects) – Shakil Jan 18 '19 at 16:42
  • My datetime.datetime object is already timezone aware, so there is no need to replace it with timezone.now(), it would produce the same error. – rollingthedice Jan 18 '19 at 16:49