Skip to content

Commit

Permalink
Fixed #17263 -- Added a warning when a naive datetime reaches the dat…
Browse files Browse the repository at this point in the history
…abase layer while time zone support is enabled.

After this commit, timezones.AdminTests will raise warnings because the sessions contrib app hasn't been upgraded to support time zones yet.
This will be fixed in an upcoming commit.



git-svn-id: http://code.djangoproject.com/svn/django/trunk@17117 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information
aaugustin committed Nov 19, 2011
1 parent 9b8e211 commit 9c30d48
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 12 deletions.
4 changes: 4 additions & 0 deletions django/db/models/fields/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import datetime
import decimal
import math
import warnings
from itertools import tee

from django.db import connection
Expand Down Expand Up @@ -789,6 +790,9 @@ def get_prep_value(self, value):
# For backwards compatibility, interpret naive datetimes in local
# time. This won't work during DST change, but we can't do much
# about it, so we let the exceptions percolate up the call stack.
warnings.warn(u"DateTimeField received a naive datetime (%s)"
u" while time zone support is active." % value,
RuntimeWarning)
default_timezone = timezone.get_default_timezone()
value = timezone.make_aware(value, default_timezone)
return value
Expand Down
19 changes: 15 additions & 4 deletions docs/topics/i18n/timezones.txt
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,9 @@ Interpretation of naive datetime objects
----------------------------------------

When :setting:`USE_TZ` is ``True``, Django still accepts naive datetime
objects, in order to preserve backwards-compatibility. It attempts to make them
aware by interpreting them in the :ref:`default time zone
<default-current-time-zone>`.
objects, in order to preserve backwards-compatibility. When the database layer
receives one, it attempts to make it aware by interpreting it in the
:ref:`default time zone <default-current-time-zone>` and raises a warning.

Unfortunately, during DST transitions, some datetimes don't exist or are
ambiguous. In such situations, pytz_ raises an exception. Other
Expand Down Expand Up @@ -421,11 +421,22 @@ with a naive datetime that you've created in your code.
So the second step is to refactor your code wherever you instanciate datetime
objects to make them aware. This can be done incrementally.
:mod:`django.utils.timezone` defines some handy helpers for compatibility
code: :func:`~django.utils.timezone.is_aware`,
code: :func:`~django.utils.timezone.now`,
:func:`~django.utils.timezone.is_aware`,
:func:`~django.utils.timezone.is_naive`,
:func:`~django.utils.timezone.make_aware`, and
:func:`~django.utils.timezone.make_naive`.

Finally, in order to help you locate code that needs upgrading, Django raises
a warning when you attempt to save a naive datetime to the database. During
development, you can turn such warnings into exceptions and get a traceback
by adding to your settings file::

import warnings
warnings.filterwarnings(
'error', r"DateTimeField received a naive datetime",
RuntimeWarning, r'django\.db\.models\.fields')

.. _pytz: http://pytz.sourceforge.net/
.. _these issues: http://pytz.sourceforge.net/#problems-with-localtime
.. _tz database: http://en.wikipedia.org/wiki/Tz_database
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
<field type="BooleanField" name="is_staff">True</field>
<field type="BooleanField" name="is_active">True</field>
<field type="BooleanField" name="is_superuser">True</field>
<field type="DateTimeField" name="last_login">2007-05-30 13:20:10</field>
<field type="DateTimeField" name="date_joined">2007-05-30 13:20:10</field>
<field type="DateTimeField" name="last_login">2001-01-01 00:00:00+00:00</field>
<field type="DateTimeField" name="date_joined">2001-01-01 00:00:00+00:00</field>
<field to="auth.group" name="groups" rel="ManyToManyRel"></field>
<field to="auth.permission" name="user_permissions" rel="ManyToManyRel"></field>
</object>
</django-objects>
</django-objects>
23 changes: 18 additions & 5 deletions tests/modeltests/timezones/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import datetime
import os
import time
import warnings

try:
import pytz
Expand Down Expand Up @@ -238,23 +239,35 @@ class NewDatabaseTests(BaseDateTimeTests):

def test_naive_datetime(self):
dt = datetime.datetime(2011, 9, 1, 13, 20, 30)
Event.objects.create(dt=dt)
with warnings.catch_warnings(record=True) as recorded:
Event.objects.create(dt=dt)
self.assertEqual(len(recorded), 1)
msg = str(recorded[0].message)
self.assertTrue(msg.startswith("DateTimeField received a naive datetime"))
event = Event.objects.get()
# naive datetimes are interpreted in local time
self.assertEqual(event.dt, dt.replace(tzinfo=EAT))

@skipUnlessDBFeature('supports_microsecond_precision')
def test_naive_datetime_with_microsecond(self):
dt = datetime.datetime(2011, 9, 1, 13, 20, 30, 405060)
Event.objects.create(dt=dt)
with warnings.catch_warnings(record=True) as recorded:
Event.objects.create(dt=dt)
self.assertEqual(len(recorded), 1)
msg = str(recorded[0].message)
self.assertTrue(msg.startswith("DateTimeField received a naive datetime"))
event = Event.objects.get()
# naive datetimes are interpreted in local time
self.assertEqual(event.dt, dt.replace(tzinfo=EAT))

@skipIfDBFeature('supports_microsecond_precision')
def test_naive_datetime_with_microsecond_unsupported(self):
dt = datetime.datetime(2011, 9, 1, 13, 20, 30, 405060)
Event.objects.create(dt=dt)
with warnings.catch_warnings(record=True) as recorded:
Event.objects.create(dt=dt)
self.assertEqual(len(recorded), 1)
msg = str(recorded[0].message)
self.assertTrue(msg.startswith("DateTimeField received a naive datetime"))
event = Event.objects.get()
# microseconds are lost during a round-trip in the database
# naive datetimes are interpreted in local time
Expand Down Expand Up @@ -294,7 +307,7 @@ def test_aware_datetime_in_other_timezone(self):
self.assertEqual(event.dt, dt)

def test_auto_now_and_auto_now_add(self):
now = datetime.datetime.utcnow().replace(tzinfo=UTC)
now = timezone.now()
past = now - datetime.timedelta(seconds=2)
future = now + datetime.timedelta(seconds=2)
Timestamp.objects.create()
Expand Down Expand Up @@ -824,7 +837,7 @@ def test_model_form(self):
class AdminTests(BaseDateTimeTests):

urls = 'modeltests.timezones.urls'
fixtures = ['users.xml']
fixtures = ['tz_users.xml']

def setUp(self):
self.client.login(username='super', password='secret')
Expand Down

0 comments on commit 9c30d48

Please sign in to comment.