Datetime Display in Django
Suppose you have instruments in different timezones taking readings. You might have the following Django data model:
class Instrument(models.Model):
timezone_utc_offset_hours = models.FloatField(...)
...
class Reading(models.Model):
instrument = models.ForeignKey(Instrument, ...)
timestamp = models.DateTimeField(auto_now_add=True)
...
Furthermore suppose settings.py contains the following:
...
USE_TZ = True
TIME_ZONE = "Europe/Paris"
...
It would be convenient to be able to display the timestamp for Readings in the timezone of the
instrument. To do this, we need to convert the UTC timestamps stored in the database into local
time of each instrument:
import datetime
class Instrument(models.Model):
...
@property
def timezone(self) -> datetime.timezone:
utc_offset = datetime.timedelta(hours=self.timezone_utc_offset_hours)
return datetime.timezone(utc_offset)
class Reading(models.Model):
...
@property
def local_timestamp(self) -> datetime.datetime:
return self.timestamp.astimezone(self.instrument.timezone)
However if you were to display local_timestamp in a template, e.g.
{{a_reading.local_timestamp | time}}, you will get the equivalent time in Paris, not the time
where the instrument is located. This is because, when USE_TZ is set, TIME_ZONE in settings
controls the default time zone used to display datetimes.
This time zone conversion can be made explicit by using a custom time format that includes the
time zone offset: {{a_reading.local_timestamp | time:"H:i O"}}. This will produce something like
16:52 +0200
regardless of the value of timezone_utc_offset_hours due to automatic conversion to TIME_ZONE.
In order to display the instrument-local time, we have to disable this automatic conversion using
the localtime template
tag:
{% load tz %}
...
{% localtime off %}
...
{{ a_reading.local_timestamp | time:"H:i O" }}
...
{% endlocaltime %}
With this change the display time is now in the instrument's timezone:
00:52 +1000
Template Tag Scoping
Note that due to the way templates work, {% localtime off %}...{% endlocaltime %}
must be inside the same block as the time display. If specified at the template level, e.g.
{% extends "base.html" %}
{% load tz %}
{% localtime off %}
{% block page-content %}
...
{{ a_reading.local_timestamp | time:"H:i O" }}
...
{% endblock %}
{% endlocaltime %}
It will have no effect inside the page-content block!