diff --git a/django/contrib/gis/db/backends/mysql/compiler.py b/django/contrib/gis/db/backends/mysql/compiler.py index 7079db9f6a..f4654eff84 100644 --- a/django/contrib/gis/db/backends/mysql/compiler.py +++ b/django/contrib/gis/db/backends/mysql/compiler.py @@ -30,3 +30,6 @@ class SQLAggregateCompiler(compiler.SQLAggregateCompiler, GeoSQLCompiler): class SQLDateCompiler(compiler.SQLDateCompiler, GeoSQLCompiler): pass + +class SQLDateTimeCompiler(compiler.SQLDateTimeCompiler, GeoSQLCompiler): + pass diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index f24df93bf4..9a607bd829 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -30,6 +30,7 @@ if (version < (1, 2, 1) or (version[:3] == (1, 2, 1) and from MySQLdb.converters import conversions, Thing2Literal from MySQLdb.constants import FIELD_TYPE, CLIENT +from django.conf import settings from django.db import utils from django.db.backends import * from django.db.backends.signals import connection_created @@ -193,6 +194,12 @@ class DatabaseFeatures(BaseDatabaseFeatures): "Confirm support for introspected foreign keys" return self._mysql_storage_engine != 'MyISAM' + @cached_property + def has_zoneinfo_database(self): + cursor = self.connection.cursor() + cursor.execute("SELECT 1 FROM mysql.time_zone LIMIT 1") + return cursor.fetchone() is not None + class DatabaseOperations(BaseDatabaseOperations): compiler_module = "django.db.backends.mysql.compiler" @@ -218,6 +225,32 @@ class DatabaseOperations(BaseDatabaseOperations): sql = "CAST(DATE_FORMAT(%s, '%s') AS DATETIME)" % (field_name, format_str) return sql + def datetime_extract_sql(self, lookup_type, field_name): + if settings.USE_TZ: + field_name = "CONVERT_TZ(%s, 'UTC', %%s)" % field_name + # http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html + if lookup_type == 'week_day': + # DAYOFWEEK() returns an integer, 1-7, Sunday=1. + # Note: WEEKDAY() returns 0-6, Monday=0. + return "DAYOFWEEK(%s)" % field_name + else: + return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), field_name) + + def datetime_trunc_sql(self, lookup_type, field_name): + if settings.USE_TZ: + field_name = "CONVERT_TZ(%s, 'UTC', %%s)" % field_name + fields = ['year', 'month', 'day', 'hour', 'minute', 'second'] + format = ('%%Y-', '%%m', '-%%d', ' %%H:', '%%i', ':%%s') # Use double percents to escape. + format_def = ('0000-', '01', '-01', ' 00:', '00', ':00') + try: + i = fields.index(lookup_type) + 1 + except ValueError: + sql = field_name + else: + format_str = ''.join([f for f in format[:i]] + [f for f in format_def[i:]]) + sql = "CAST(DATE_FORMAT(%s, '%s') AS DATETIME)" % (field_name, format_str) + return sql + def date_interval_sql(self, sql, connector, timedelta): return "(%s %s INTERVAL '%d 0:0:%d:%d' DAY_MICROSECOND)" % (sql, connector, timedelta.days, timedelta.seconds, timedelta.microseconds) @@ -314,11 +347,10 @@ class DatabaseOperations(BaseDatabaseOperations): # MySQL doesn't support microseconds return six.text_type(value.replace(microsecond=0)) - def year_lookup_bounds(self, value): + def year_lookup_bounds_for_datetime_field(self, value): # Again, no microseconds - first = '%s-01-01 00:00:00' - second = '%s-12-31 23:59:59.99' - return [first % value, second % value] + first, second = super(DatabaseOperations, self).year_lookup_bounds_for_datetime_field(value) + return [first.replace(microsecond=0), second.replace(microsecond=0)] def max_name_length(self): return 64 diff --git a/django/db/backends/mysql/compiler.py b/django/db/backends/mysql/compiler.py index d8e9b3a202..f4c5563eb2 100644 --- a/django/db/backends/mysql/compiler.py +++ b/django/db/backends/mysql/compiler.py @@ -31,3 +31,6 @@ class SQLAggregateCompiler(compiler.SQLAggregateCompiler, SQLCompiler): class SQLDateCompiler(compiler.SQLDateCompiler, SQLCompiler): pass + +class SQLDateTimeCompiler(compiler.SQLDateTimeCompiler, SQLCompiler): + pass