Search code examples
pythondjangodjango-modelstimezonepytz

Django Opening Hours With UTC Support/Timezone Issue


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.


Solution

  • 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.