I have a Django application where users can setup stores. I recently added functionality to support opening hours following the advice on this thread - Business Opening hours in Django. My model looks like this:
class LocationHours(SafeDeleteModel):
location = models.ForeignKey(Location, related_name="hours", on_delete=models.CASCADE)
weekday = models.IntegerField(choices=WEEKDAYS, blank=False, null=False)
start_time = models.TimeField(blank=False, null=False, help_text="Opening time (00:00 format)")
end_time = models.TimeField(blank=False, null=False, help_text="Closing time (00:00 format)")
class Meta:
ordering = ('weekday', 'start_time')
unique_together = ('location', 'weekday', 'start_time', 'end_time')
verbose_name_plural = "Location hours"
Process goes like this - these times are entered in a form by the end user and thus assumed to be localtime, most datetimes/times being used in my application are in UTC. I need to compare the two often so originally, I thought I could figure out the timezone of each location object, then whenever I compare something to these OpeningHours, I can just use the given time and the tz (on the associated Location object) to calculate the time in UTC and then it's just a regular datetime comparison.
I wrote the following function to try and fix this:
def is_dt_within_location_hours(location, dt):
# see if time falls within hours
hours = location.hours
if hours.count() > 0:
for hour in hours.all():
dt = dt.astimezone(hour.location.timezone)
if dt.weekday() == hour.weekday:
if hour.start_time < dt.time() < hour.end_time:
return True
return False
else: # this location has no hours
return True
I thought this worked however has some issues.
Primary issue is this - when the Location objects are originally made or edited, I look up the timezone it's in (using the timezonefinder package) and store that in the Location
object (using a TimeZoneField
) at that time. This is to say, it will not auto update for DST or anything like that as far as I know. I could figure out the timezone everytime I call the above function however I call said function A LOT such that resource wise I'd like to say this is borderline not an option.
I imagine I could find a way to figure out the localtime at the moment they create an OpeningHours object and that way I could just convert to UTC and save it then but I don't know a good way to do that.
I'm thinking now I may need to scrap my entire solution and start from scratch but any advice is really helpful I've been struggling with this for a while.
You're doing it the right way.
You're worried about the timezone offset changing (as with DST) in between the time you record the Location
and when you do the computation. But a timezone (represented by a name like "America/Chicago") isn't just an offset, it's a set of rules for computing the local time at any point in history. So it will do the right thing regardless of when you happened to record the timezone name.
A few other notes on the code you posted:
You probably want to make LocationHours
unique on just location
and weekeday
, unless you're purposely trying to allow multiple opening hours for the same location on the same weekday.
Your is_dt_within_location_hours()
is fairly inefficient. You don't need to fetch all the LocationHours
objects and re-compute the weekday each time. Instead, first compute the local time, then filter location.hours
to only include the LocationHours
objects on that weekday.