Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Fixed #17755 -- Ensured datetime objects that bypass the model layer …

…(for instance, in raw SQL queries) are converted to UTC before sending them to the database when time zone support is enabled. Thanks Anssi for the report.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@17596 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit ce88b57b9aca0325e5b90944019f602a92f93475 1 parent 7061da5
@aaugustin aaugustin authored
View
35 django/db/backends/mysql/base.py
@@ -4,6 +4,7 @@
Requires MySQLdb: http://sourceforge.net/projects/mysql-python
"""
+import datetime
import re
import sys
@@ -24,6 +25,7 @@
from MySQLdb.converters import conversions
from MySQLdb.constants import FIELD_TYPE, CLIENT
+from _mysql import string_literal
from django.db import utils
from django.db.backends import *
@@ -33,7 +35,7 @@
from django.db.backends.mysql.introspection import DatabaseIntrospection
from django.db.backends.mysql.validation import DatabaseValidation
from django.utils.safestring import SafeString, SafeUnicode
-from django.utils.timezone import is_aware, is_naive, utc
+from django.utils import timezone
# Raise exceptions for database warnings if DEBUG is on
from django.conf import settings
@@ -45,15 +47,27 @@
IntegrityError = Database.IntegrityError
# It's impossible to import datetime_or_None directly from MySQLdb.times
-datetime_or_None = conversions[FIELD_TYPE.DATETIME]
+parse_datetime = conversions[FIELD_TYPE.DATETIME]
-def datetime_or_None_with_timezone_support(value):
- dt = datetime_or_None(value)
+def parse_datetime_with_timezone_support(value):
+ dt = parse_datetime(value)
# Confirm that dt is naive before overwriting its tzinfo.
- if dt is not None and settings.USE_TZ and is_naive(dt):
- dt = dt.replace(tzinfo=utc)
+ if dt is not None and settings.USE_TZ and timezone.is_naive(dt):
+ dt = dt.replace(tzinfo=timezone.utc)
return dt
+def adapt_datetime_with_timezone_support(value, conv):
+ # Equivalent to DateTimeField.get_db_prep_value. Used only by raw SQL.
+ if settings.USE_TZ:
+ if timezone.is_naive(value):
+ warnings.warn(u"SQLite 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)
+ value = value.astimezone(timezone.utc).replace(tzinfo=None)
+ return string_literal(value.strftime("%Y-%m-%d %H:%M:%S"), conv)
+
# MySQLdb-1.2.1 returns TIME columns as timedelta -- they are more like
# timedelta in terms of actual behavior as they are signed and include days --
# and Django expects time, so we still need to override that. We also need to
@@ -66,7 +80,8 @@ def datetime_or_None_with_timezone_support(value):
FIELD_TYPE.TIME: util.typecast_time,
FIELD_TYPE.DECIMAL: util.typecast_decimal,
FIELD_TYPE.NEWDECIMAL: util.typecast_decimal,
- FIELD_TYPE.DATETIME: datetime_or_None_with_timezone_support,
+ FIELD_TYPE.DATETIME: parse_datetime_with_timezone_support,
+ datetime.datetime: adapt_datetime_with_timezone_support,
})
# This should match the numerical portion of the version numbers (we can treat
@@ -268,9 +283,9 @@ def value_to_db_datetime(self, value):
return None
# MySQL doesn't support tz-aware datetimes
- if is_aware(value):
+ if timezone.is_aware(value):
if settings.USE_TZ:
- value = value.astimezone(utc).replace(tzinfo=None)
+ value = value.astimezone(timezone.utc).replace(tzinfo=None)
else:
raise ValueError("MySQL backend does not support timezone-aware datetimes when USE_TZ is False.")
@@ -282,7 +297,7 @@ def value_to_db_time(self, value):
return None
# MySQL doesn't support tz-aware times
- if is_aware(value):
+ if timezone.is_aware(value):
raise ValueError("MySQL backend does not support timezone-aware times.")
# MySQL doesn't support microseconds
View
23 django/db/backends/oracle/base.py
@@ -52,7 +52,7 @@ def _setup_environment(environ):
from django.db.backends.oracle.creation import DatabaseCreation
from django.db.backends.oracle.introspection import DatabaseIntrospection
from django.utils.encoding import smart_str, force_unicode
-from django.utils.timezone import is_aware, is_naive, utc
+from django.utils import timezone
DatabaseError = Database.DatabaseError
IntegrityError = Database.IntegrityError
@@ -339,9 +339,9 @@ def value_to_db_datetime(self, value):
return None
# Oracle doesn't support tz-aware datetimes
- if is_aware(value):
+ if timezone.is_aware(value):
if settings.USE_TZ:
- value = value.astimezone(utc).replace(tzinfo=None)
+ value = value.astimezone(timezone.utc).replace(tzinfo=None)
else:
raise ValueError("Oracle backend does not support timezone-aware datetimes when USE_TZ is False.")
@@ -355,7 +355,7 @@ def value_to_db_time(self, value):
return datetime.datetime.strptime(value, '%H:%M:%S')
# Oracle doesn't support tz-aware times
- if is_aware(value):
+ if timezone.is_aware(value):
raise ValueError("Oracle backend does not support timezone-aware times.")
return datetime.datetime(1900, 1, 1, value.hour, value.minute,
@@ -561,6 +561,17 @@ class OracleParam(object):
"""
def __init__(self, param, cursor, strings_only=False):
+ # With raw SQL queries, datetimes can reach this function
+ # without being converted by DateTimeField.get_db_prep_value.
+ if settings.USE_TZ and isinstance(param, datetime.datetime):
+ if timezone.is_naive(param):
+ warnings.warn(u"Oracle received a naive datetime (%s)"
+ u" while time zone support is active." % param,
+ RuntimeWarning)
+ default_timezone = timezone.get_default_timezone()
+ param = timezone.make_aware(param, default_timezone)
+ param = param.astimezone(timezone.utc).replace(tzinfo=None)
+
if hasattr(param, 'bind_parameter'):
self.smart_str = param.bind_parameter(cursor)
else:
@@ -783,8 +794,8 @@ def _rowfactory(row, cursor):
# of "dates" queries, which are returned as DATETIME.
elif desc[1] in (Database.TIMESTAMP, Database.DATETIME):
# Confirm that dt is naive before overwriting its tzinfo.
- if settings.USE_TZ and value is not None and is_naive(value):
- value = value.replace(tzinfo=utc)
+ if settings.USE_TZ and value is not None and timezone.is_naive(value):
+ value = value.replace(tzinfo=timezone.utc)
elif desc[1] in (Database.STRING, Database.FIXED_CHAR,
Database.LONG_STRING):
value = to_unicode(value)
View
27 django/db/backends/sqlite3/base.py
@@ -19,7 +19,7 @@
from django.db.backends.sqlite3.introspection import DatabaseIntrospection
from django.utils.dateparse import parse_date, parse_datetime, parse_time
from django.utils.safestring import SafeString
-from django.utils.timezone import is_aware, is_naive, utc
+from django.utils import timezone
try:
try:
@@ -37,10 +37,22 @@
def parse_datetime_with_timezone_support(value):
dt = parse_datetime(value)
# Confirm that dt is naive before overwriting its tzinfo.
- if dt is not None and settings.USE_TZ and is_naive(dt):
- dt = dt.replace(tzinfo=utc)
+ if dt is not None and settings.USE_TZ and timezone.is_naive(dt):
+ dt = dt.replace(tzinfo=timezone.utc)
return dt
+def adapt_datetime_with_timezone_support(value):
+ # Equivalent to DateTimeField.get_db_prep_value. Used only by raw SQL.
+ if settings.USE_TZ:
+ if timezone.is_naive(value):
+ warnings.warn(u"SQLite 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)
+ value = value.astimezone(timezone.utc).replace(tzinfo=None)
+ return value.isoformat(" ")
+
Database.register_converter("bool", lambda s: str(s) == '1')
Database.register_converter("time", parse_time)
Database.register_converter("date", parse_date)
@@ -48,13 +60,14 @@ def parse_datetime_with_timezone_support(value):
Database.register_converter("timestamp", parse_datetime_with_timezone_support)
Database.register_converter("TIMESTAMP", parse_datetime_with_timezone_support)
Database.register_converter("decimal", util.typecast_decimal)
+Database.register_adapter(datetime.datetime, adapt_datetime_with_timezone_support)
Database.register_adapter(decimal.Decimal, util.rev_typecast_decimal)
if Database.version_info >= (2, 4, 1):
# Starting in 2.4.1, the str type is not accepted anymore, therefore,
# we convert all str objects to Unicode
# As registering a adapter for a primitive type causes a small
# slow-down, this adapter is only registered for sqlite3 versions
- # needing it.
+ # needing it (Python 2.6 and up).
Database.register_adapter(str, lambda s: s.decode('utf-8'))
Database.register_adapter(SafeString, lambda s: s.decode('utf-8'))
@@ -147,9 +160,9 @@ def value_to_db_datetime(self, value):
return None
# SQLite doesn't support tz-aware datetimes
- if is_aware(value):
+ if timezone.is_aware(value):
if settings.USE_TZ:
- value = value.astimezone(utc).replace(tzinfo=None)
+ value = value.astimezone(timezone.utc).replace(tzinfo=None)
else:
raise ValueError("SQLite backend does not support timezone-aware datetimes when USE_TZ is False.")
@@ -160,7 +173,7 @@ def value_to_db_time(self, value):
return None
# SQLite doesn't support tz-aware datetimes
- if is_aware(value):
+ if timezone.is_aware(value):
raise ValueError("SQLite backend does not support timezone-aware times.")
return unicode(value)
View
18 tests/modeltests/timezones/tests.py
@@ -263,6 +263,15 @@ def test_query_dates(self):
self.assertQuerysetEqual(Event.objects.dates('dt', 'day'),
[datetime.datetime(2011, 1, 1)], transform=lambda d: d)
+ def test_raw_sql(self):
+ # Regression test for #17755
+ dt = datetime.datetime(2011, 9, 1, 13, 20, 30)
+ event = Event.objects.create(dt=dt)
+ self.assertQuerysetEqual(
+ Event.objects.raw('SELECT * FROM timezones_event WHERE dt = %s', [dt]),
+ [event],
+ transform=lambda d: d)
+
LegacyDatabaseTests = override_settings(USE_TZ=False)(LegacyDatabaseTests)
@@ -473,6 +482,15 @@ def test_query_dates(self):
datetime.datetime(2011, 1, 1, tzinfo=UTC)],
transform=lambda d: d)
+ def test_raw_sql(self):
+ # Regression test for #17755
+ dt = datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT)
+ event = Event.objects.create(dt=dt)
+ self.assertQuerysetEqual(
+ Event.objects.raw('SELECT * FROM timezones_event WHERE dt = %s', [dt]),
+ [event],
+ transform=lambda d: d)
+
def test_null_datetime(self):
# Regression for #17294
e = MaybeEvent.objects.create()

0 comments on commit ce88b57

Please sign in to comment.
Something went wrong with that request. Please try again.