Up to now I thought both ways to create a timezone aware datetime are equal.
But they are not:
import datetime
from django.utils.timezone import make_aware, get_current_timezone
make_aware(datetime.datetime(1999, 1, 1, 0, 0, 0), get_current_timezone())
datetime.datetime(1999, 1, 1, 0, 0, 0, tzinfo=get_current_timezone())
datetime.datetime(1999, 1, 1, 0, 0, tzinfo=<DstTzInfo 'Europe/Berlin' CET+1:00:00 STD>)
datetime.datetime(1999, 1, 1, 0, 0, tzinfo=<DstTzInfo 'Europe/Berlin' LMT+0:53:00 STD>)
In the Django Admin GUI second way creates this (German date format dd.mm.YYYY):
01.01.1999 00:07:00
Why are there 7 minutes difference if I use this:
datetime.datetime(1999, 1, 1, 0, 0, 0, tzinfo=get_current_timezone())
This happens on Django 3.2 and lower, which rely on the pytz library. In Django 4 (unless you enable to setting to use the deprecated library), the output of the two examples you give is identical.
In Django 3.2 and below, the variance arises because the localised time is built in two different ways. When using
make_aware
, it is done by calling thelocalize()
method on thepytz
timezone instance. In the second version, it's done by passing atzinfo
object directly to thedatetime
constructor.The difference between the two is well illustrated in this blog post:
Exactly the same thing is happening with your
Europe/Berlin
example.pytz
is eagerly fetching the first entry in its database, which is a pre-1983 solar time, which was 53 minutes and 28 seconds ahead of Greenwich Mean Time (GMT). This is obviously inappropriate given the date - but thetzinfo
isn't aware of the date you are using unless you pass it tolocalize()
.This is the difference between your two approaches. Using
make_aware
correctly callslocalize()
on the object. Assigning thetzinfo
directly to thedatetime
object, however, doesn't, and results inpytz
using the (wrong) time zone information because it was simply the first entry for that zone in its database.The pytz documentation obliquely refers to this as well:
It is actually because of these and several other bugs in the
pytz
implementation that Django dropped it in favour of Python's built-inzoneinfo
module.More from that blog post:
Passing a
pytz
tzinfo
object directly to adatetime
constructor is incorrect. You must calllocalize()
on thetzinfo
class, passing it the date. The correct way to initialise the datetime in your second example is:... which matches what
make_aware
produces.