Using timezone-aware DateTime in Django

When working with models in Django with DateTimeField such as created_at, updated_at, and so on, we often come across the issue of supporting different timezones, especially when your intended audience is spread out in multiple timezones.

Quite often, during the database/backend design process, you will find yourself deciding on this topic by asking questions such as: "do we save timestamps in our own current local timezone, and then translate it to the user's timezone on the frontend?"

This is where Django's i18n (shorthand for "internationalization") support for timezone comes in handy.

Django stores datetime information in UTC in the database, uses timezone-aware datetime objects internally, and translates them to the end user’s time zone in templates and forms.

(Reference: https://docs.djangoproject.com/en/dev/topics/i18n/timezones/)

Setting timezone support in settings.py

In settings.py, we can enable support for timezones by setting USE_TZ and providing which TIME_ZONE this Django instance should use, for instance:

# settings.py

USE_TZ = True
TIME_ZONE = "America/New_York"

(Note: USE_TZ is set to True and TIME_ZONE "UTC" by default when you run django-admin startproject.)

Django's time zone support uses pytz by default and a list of TIME_ZONE values can be found by running:

import pytz

print(pytz.all_timezones)  
# ["Africa/Abidjan", "Africa/Accra", ..., "Asia/Seoul",  "UTC", ...]

"Naïve" DateTime objects

To create a datetime object, we simply use the datetime.datetime.now(), i.e.

from datetime import datetime

start_time = datetime.now()

While this is sufficient for most cases, the start_time object here is considered as "naïve". When you attempt to store this naive timestamp to a model instance, Django will complain about timezone support with the following message:

RuntimeWarning: DateTimeField start_time received a naive datetime (2021-01-23 12:34:56) while time zone support is active.

Python's datetime object has a property named tzinfo (stands for timezone information), that provides an offset for the timezone from UTC (Coordinated Universal Time). For example, US Eastern Standard Time is UTC -05:00, and US Eastern Daylight Savings Time is UTC -04:00. (Note how the UTC "zero" is absolute and thus the local time change affects the offset instead).

"Aware" DateTime objects

A timezone-aware DateTime object simply means that it has a tzinfo attribute.

Django conveniently provides timezone support via its utils module; i.e. to convert a DateTime object from above snippet:

from datetime import datetime
from django.utils import timezone

start_time = datetime.now()  # naive

aware_start_time = timezone.make_aware(datetime.now())  # aware