Search code examples
pythondjangodatetimetimezonepytz

Why are django.utils.timezone.make_aware and pytz.localize returning different results when dealing with DST?


I'm using django 1.11 and pytz 2018.6

I'm having some problems understanding how django is dealing with DST.

My main problem is localizing the date 2018-11-04 00:00:00 in the America/Sao_Paulo timezone. According to the latest pytz version this is the date when DST starts in this timezone in 2018.

Well, on my application I started seeing the pytz.exceptions.NonExistentTimeError exception when trying to localize the mentioned date. The following code reproduces this exception:

import os
import datetime
import django
import pytz
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")
django.setup()
from django.utils.timezone import make_aware

sp = pytz.timezone('America/Sao_Paulo')
dst_start_date = datetime.datetime(2018, 11, 4, 0, 0, 0)
make_aware(dst_start_date, sp)
# Exception raised: pytz.exceptions.NonExistentTimeError: 2018-11-04 00:00:00

However, if I try to localize using pytz.localize instead of make_aware I get different results:

sp.localize(dst_start_date) # Returns 2018-11-04 00:00:00-03:00

I expected to receive the same exception when trying to localize. But it didn't raise the exception and actually returned a wrong result (the -03:00 offset is when we're not on DST. On the specific date I was expecting the 2018-11-04 00:00:00-03:00 date to be converted to 2018-11-04 00:00:00-02:00).

This confuses me, because reading the make_aware code in django.utils.timezone I understood the same pytz.tzinfo.localize method is called.

# django.utils.timezone
def make_aware(value, timezone=None, is_dst=None):
    """
    Makes a naive datetime.datetime in a given time zone aware.
    """
    if timezone is None:
        timezone = get_current_timezone()
    if hasattr(timezone, 'localize'):
        # This method is available for pytz time zones.
        return timezone.localize(value, is_dst=is_dst)
    else:
        # Check that we won't overwrite the timezone of an aware datetime.
        if is_aware(value):
            raise ValueError(
                "make_aware expects a naive datetime, got %s" % value)
        # This may be wrong around DST changes!
        return value.replace(tzinfo=timezone)

Why are both results different? Why I'm not getting an exception when manually trying to localize the date 2018-11-04 00:00:00 to America/Sao_Paulo?

Please make sure you have the latest pytz version (pip install pytz --upgrade) before trying this code because we had a change on the DST date this year.


Solution

  • The difference is the is_dst parameter passed to localize. When you call it yourself but leave it off, it defaults to False. In the make_aware code you posted it defaults to None.