It's About Time

 

Getting to grips with timezones in Django

 

David Seddon

david@seddonym.me

http://seddonym.me

Do you prefer to have timezones enabled on your sites?

 

 

1. Yes

2. No

3. It depends on the site

4. I don't really think about it

Have you ever seen
this warning?

DateTimeField MyModel.my_datetime received a
naive datetime (2016-04-09 16:19:52.557550)
while time zone support is active.

In Django, is timezone support enabled by default?

1. Yes

2. No

3. I have no idea

4. None of the above

Timezones are something we would rather not
think about

This talk

  1. Timezone settings
     
  2. Naive versus aware datetimes
     
  3. Datetimes in models, forms
    and templates

Part 1

Timezone settings
 

Two settings you should care about

USE_TZ

Whether or not timezones
are enabled.

TIME_ZONE

The default time zone.


USE_TZ = True

TIME_ZONE = 'Europe/London'

USE_TZ is False by default, but True in projects
created using django-admin.py startproject.

Part 2

Naive vs. aware datetimes
 

12 April 2016, 7.30pm

A naive datetime

Naive datetimes do not represent moments in time (even if it looks like they do).

Instead, they are calendar events.

Getting a naive datetime

>>> from datetime import datetime

>>> naive_datetime = datetime.now()
>>> naive_datetime
datetime.datetime(2016, 4, 12, 19, 30, 24, 247648)

>>> from django.utils import timezone
>>> timezone.is_naive(naive_datetime)
True

12 April 2016, 7.30pm, London time

Some aware datetimes

Aware datetimes represent moments in time.

12 April 2016, 6.30pm, UTC

12 April 2016, 8.30pm, Paris time

Getting a aware datetime

>>> from django.utils import timezone
>>>
>>> aware_datetime = timezone.now()
>>> aware_datetime
datetime.datetime(2016, 4, 12, 19, 30, 24, 247648, 
tzinfo=<UTC>)

>>> timezone.is_aware(aware_datetime)
True
# settings.py

USE_TZ = True

If USE_TZ is False, timezone.now() will return a naive datetime.

Making datetimes aware

>>> from django.utils import timezone

>>> from datetime import datetime

>>> naive_datetime = datetime.now()

>>> naive_datetime
datetime.datetime(2016, 4, 12, 19, 30, 24, 247648)

>>> aware_datetime = timezone.make_aware(naive_datetime)

>>> aware_datetime
datetime.datetime(2016, 4, 12, 19, 30, 24, 247648, 
tzinfo=<DstTzInfo 'Europe/London' BST+1:00:00 DST>)

make_aware() will use the current timezone by default,
make sure it's the correct one!

Current vs Default timezone

The default time zone is the time zone defined by the TIME_ZONE setting.

 

The current time zone is the time zone that’s used for rendering.  This can be something that your application can set.

# my_app/views.py

import pytz
from django.utils import timezone


def my_view(request):
    timezone.activate(pytz.timezone('Europe/London'))
    # etc.

Dates and times

Dates and times are not datetimes.

 

They are calendar events, and are always naive in Django.

 

Don't combine DateFields and TimeFields, to depict datetimes.

Part 3

Datetimes in models, forms and templates
 

Timezone-aware Django

April 12th 2016, 7.30pm

April 12th 2016, 7.30pm Europe/London

User submits datetime in form

Django assumes the datetime is in the current timezone

April 12th 2016, 6.30pm UTC

Django converts the datetime to UTC before storing in database

April 12th 2016, 6.30pm UTC

Django fetches the datetime from the database, as UTC

April 12th 2016, 7.30pm

Django renders the datetime in
 the current timezone

All you need to remember

  1. User inputted datetimes are assumed to be in the current timezone.
  2. Datetimes are stored in the database in UTC.
  3. Datetimes are rendered in templates in the current timezone.

A quiz

Assuming we are in British Summertime,
and TIME_ZONE = 'Europe/London':

Question One

  1. With timezones disabled, save to a model field a datetime of 7.30pm today.
  2. Now, render the model field to a template. What do you see?

Answer: 7.30pm today.

Assuming we are in British Summertime,
and TIME_ZONE = 'Europe/London':

Question Two

  1. Now, enable timezone support.
  2. Render the datetime again. What do
    you see?

Answer: 8.30pm today.

Assuming we are in British Summertime,
and TIME_ZONE = 'Europe/London':

Question Three

  1. Resave the model with 7.30pm today, London time.
  2. Render the datetime again. What do
    you see?

Answer: 7.30pm today.

Assuming we are in British Summertime,
and TIME_ZONE = 'Europe/London':

Question Four

  1. Resave the model with 7.30pm today, Paris time.
  2. Render the datetime again. What do
    you see?

Answer: 6.30pm today.

Assuming we are in British Summertime,
and TIME_ZONE = 'Europe/London':

Question Five

  1. Save the model with 7.30pm today, London time.
  2. Turn off time zone support, and reload the model.
  3. Render the datetime again. What do
    you see?

Answer: 6.30pm today.

Migrating to a timezone aware site

You can't just change the USE_TZ setting.  It will probably mess things up, because Django will assume that all preexisting datetimes are in UTC.

 

Be sure to write a migration that corrects the datetimes so they have the correct timezone.

Putting it all together

USE_TZ = False USE_TZ = True
Naive datetimes stored in the database As is Assumed to be UTC, with a warning
Aware datetimes stored in the database Raises ValueError Converted to UTC
Datetimes retrieved from database Naive datetime Aware datetime in UTC
Datetime form fields will assume Naive datetime Aware datetime in current timezone
Naive datetime in template As is As is
Aware datetime in template As is Converted to current timezone
datetime.now() Naive datetime Naive datetime
timezone.now() Naive datetime Aware datetime in UTC

Some closing remarks

  • Use pytz
  • Always enable timezone handling
  • Don't enable/disable timezones without migrating your datetimes
  • Use timezone.now(), never datetime.now()
  • Use django.timezone.utils for helpful utilities.

 

https://slides.com/davidseddon/about-time

 

 

 

 

David Seddon

david@seddonym.me

http://seddonym.me

 

Made with Slides.com