Reputation: 1125
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
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
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