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 thepytztimezone instance. In the second version, it's done by passing atzinfoobject directly to thedatetimeconstructor.The difference between the two is well illustrated in this blog post:
Exactly the same thing is happening with your
Europe/Berlinexample.pytzis 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 thetzinfoisn't aware of the date you are using unless you pass it tolocalize().This is the difference between your two approaches. Using
make_awarecorrectly callslocalize()on the object. Assigning thetzinfodirectly to thedatetimeobject, however, doesn't, and results inpytzusing 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
pytzimplementation that Django dropped it in favour of Python's built-inzoneinfomodule.More from that blog post:
Passing a
pytztzinfoobject directly to adatetimeconstructor is incorrect. You must calllocalize()on thetzinfoclass, passing it the date. The correct way to initialise the datetime in your second example is:... which matches what
make_awareproduces.