From 9c30d48b45cfe694233a9e636bb516bdd9200da9 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 19 Nov 2011 23:27:20 +0000 Subject: [PATCH] Fixed #17263 -- Added a warning when a naive datetime reaches the database 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 --- django/db/models/fields/__init__.py | 4 ++++ docs/topics/i18n/timezones.txt | 19 +++++++++++---- .../fixtures/{users.xml => tz_users.xml} | 6 ++--- tests/modeltests/timezones/tests.py | 23 +++++++++++++++---- 4 files changed, 40 insertions(+), 12 deletions(-) rename tests/modeltests/timezones/fixtures/{users.xml => tz_users.xml} (81%) diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 5e7cf804e89..6824ba17f20 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -2,6 +2,7 @@ import copy import datetime import decimal import math +import warnings from itertools import tee from django.db import connection @@ -789,6 +790,9 @@ class DateTimeField(DateField): # 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 diff --git a/docs/topics/i18n/timezones.txt b/docs/topics/i18n/timezones.txt index 18d30b941ae..52c26aa0388 100644 --- a/docs/topics/i18n/timezones.txt +++ b/docs/topics/i18n/timezones.txt @@ -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 -`. +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 ` and raises a warning. Unfortunately, during DST transitions, some datetimes don't exist or are ambiguous. In such situations, pytz_ raises an exception. Other @@ -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 diff --git a/tests/modeltests/timezones/fixtures/users.xml b/tests/modeltests/timezones/fixtures/tz_users.xml similarity index 81% rename from tests/modeltests/timezones/fixtures/users.xml rename to tests/modeltests/timezones/fixtures/tz_users.xml index 6cf441f01ef..4311ef4c5fa 100644 --- a/tests/modeltests/timezones/fixtures/users.xml +++ b/tests/modeltests/timezones/fixtures/tz_users.xml @@ -9,9 +9,9 @@ True True True - 2007-05-30 13:20:10 - 2007-05-30 13:20:10 + 2001-01-01 00:00:00+00:00 + 2001-01-01 00:00:00+00:00 - \ No newline at end of file + diff --git a/tests/modeltests/timezones/tests.py b/tests/modeltests/timezones/tests.py index 547680c7287..bf0520adf80 100644 --- a/tests/modeltests/timezones/tests.py +++ b/tests/modeltests/timezones/tests.py @@ -3,6 +3,7 @@ from __future__ import with_statement import datetime import os import time +import warnings try: import pytz @@ -238,7 +239,11 @@ 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)) @@ -246,7 +251,11 @@ class NewDatabaseTests(BaseDateTimeTests): @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)) @@ -254,7 +263,11 @@ class NewDatabaseTests(BaseDateTimeTests): @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 @@ -294,7 +307,7 @@ class NewDatabaseTests(BaseDateTimeTests): 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() @@ -824,7 +837,7 @@ NewFormsTests = override_settings(DATETIME_FORMAT='c', USE_L10N=False, USE_TZ=Tr class AdminTests(BaseDateTimeTests): urls = 'modeltests.timezones.urls' - fixtures = ['users.xml'] + fixtures = ['tz_users.xml'] def setUp(self): self.client.login(username='super', password='secret')