Skip to content

Commit 25d8b39

Browse files
committed
Fixed datetime issues
1 parent 9ec251a commit 25d8b39

File tree

1 file changed

+161
-71
lines changed

1 file changed

+161
-71
lines changed

mssql/operations.py

Lines changed: 161 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
from django import VERSION as django_version
1616
import pytz
1717

18+
DJANGO41 = django_version >= (4, 1)
19+
1820

1921
class DatabaseOperations(BaseDatabaseOperations):
2022
compiler_module = 'mssql.compiler'
@@ -32,6 +34,12 @@ def _convert_field_to_tz(self, field_name, tzname):
3234
field_name = 'DATEADD(second, %d, %s)' % (offset, field_name)
3335
return field_name
3436

37+
def _convert_sql_to_tz(self, sql, params, tzname):
38+
if tzname and settings.USE_TZ and self.connection.timezone_name != tzname:
39+
offset = self._get_utcoffset(tzname)
40+
sql = 'DATEADD(second, %d, %s)' % (offset, sql)
41+
return sql, params
42+
3543
def _get_utcoffset(self, tzname):
3644
"""
3745
Returns UTC offset for given time zone in seconds
@@ -125,17 +133,32 @@ def convert_uuidfield_value(self, value, expression, connection):
125133
def convert_booleanfield_value(self, value, expression, connection):
126134
return bool(value) if value in (0, 1) else value
127135

128-
def date_extract_sql(self, lookup_type, field_name):
129-
if lookup_type == 'week_day':
130-
return "DATEPART(weekday, %s)" % field_name
131-
elif lookup_type == 'week':
132-
return "DATEPART(iso_week, %s)" % field_name
133-
elif lookup_type == 'iso_week_day':
134-
return "DATEPART(weekday, DATEADD(day, -1, %s))" % field_name
135-
elif lookup_type == 'iso_year':
136-
return "YEAR(DATEADD(day, 26 - DATEPART(isoww, %s), %s))" % (field_name, field_name)
137-
else:
138-
return "DATEPART(%s, %s)" % (lookup_type, field_name)
136+
137+
if DJANGO41:
138+
def date_extract_sql(self, lookup_type, sql, params):
139+
if lookup_type == 'week_day':
140+
sql = "DATEPART(weekday, %s)" % sql
141+
elif lookup_type == 'week':
142+
sql = "DATEPART(iso_week, %s)" % sql
143+
elif lookup_type == 'iso_week_day':
144+
sql = "DATEPART(weekday, DATEADD(day, -1, %s))" % sql
145+
elif lookup_type == 'iso_year':
146+
sql = "YEAR(DATEADD(day, 26 - DATEPART(isoww, %s), %s))" % (sql, sql)
147+
else:
148+
sql = "DATEPART(%s, %s)" % (lookup_type, sql)
149+
return sql, params
150+
else:
151+
def date_extract_sql(self, lookup_type, field_name):
152+
if lookup_type == 'week_day':
153+
return "DATEPART(weekday, %s)" % field_name
154+
elif lookup_type == 'week':
155+
return "DATEPART(iso_week, %s)" % field_name
156+
elif lookup_type == 'iso_week_day':
157+
return "DATEPART(weekday, DATEADD(day, -1, %s))" % field_name
158+
elif lookup_type == 'iso_year':
159+
return "YEAR(DATEADD(day, 26 - DATEPART(isoww, %s), %s))" % (field_name, field_name)
160+
else:
161+
return "DATEPART(%s, %s)" % (lookup_type, field_name)
139162

140163
def date_interval_sql(self, timedelta):
141164
"""
@@ -147,50 +170,100 @@ def date_interval_sql(self, timedelta):
147170
sql = 'DATEADD(microsecond, %d%%s, CAST(%s AS datetime2))' % (timedelta.microseconds, sql)
148171
return sql
149172

150-
def date_trunc_sql(self, lookup_type, field_name, tzname=None):
151-
field_name = self._convert_field_to_tz(field_name, tzname)
152-
CONVERT_YEAR = 'CONVERT(varchar, DATEPART(year, %s))' % field_name
153-
CONVERT_QUARTER = 'CONVERT(varchar, 1+((DATEPART(quarter, %s)-1)*3))' % field_name
154-
CONVERT_MONTH = 'CONVERT(varchar, DATEPART(month, %s))' % field_name
155-
CONVERT_WEEK = "DATEADD(DAY, (DATEPART(weekday, %s) + 5) %%%% 7 * -1, %s)" % (field_name, field_name)
156-
157-
if lookup_type == 'year':
158-
return "CONVERT(datetime2, %s + '/01/01')" % CONVERT_YEAR
159-
if lookup_type == 'quarter':
160-
return "CONVERT(datetime2, %s + '/' + %s + '/01')" % (CONVERT_YEAR, CONVERT_QUARTER)
161-
if lookup_type == 'month':
162-
return "CONVERT(datetime2, %s + '/' + %s + '/01')" % (CONVERT_YEAR, CONVERT_MONTH)
163-
if lookup_type == 'week':
164-
return "CONVERT(datetime2, CONVERT(varchar, %s, 112))" % CONVERT_WEEK
165-
if lookup_type == 'day':
166-
return "CONVERT(datetime2, CONVERT(varchar(12), %s, 112))" % field_name
167-
168-
def datetime_cast_date_sql(self, field_name, tzname):
169-
field_name = self._convert_field_to_tz(field_name, tzname)
170-
sql = 'CAST(%s AS date)' % field_name
171-
return sql
172-
173-
def datetime_cast_time_sql(self, field_name, tzname):
174-
field_name = self._convert_field_to_tz(field_name, tzname)
175-
sql = 'CAST(%s AS time)' % field_name
176-
return sql
177-
178-
def datetime_extract_sql(self, lookup_type, field_name, tzname):
179-
field_name = self._convert_field_to_tz(field_name, tzname)
180-
return self.date_extract_sql(lookup_type, field_name)
181-
182-
def datetime_trunc_sql(self, lookup_type, field_name, tzname):
183-
field_name = self._convert_field_to_tz(field_name, tzname)
184-
sql = ''
185-
if lookup_type in ('year', 'quarter', 'month', 'week', 'day'):
186-
sql = self.date_trunc_sql(lookup_type, field_name)
187-
elif lookup_type == 'hour':
188-
sql = "CONVERT(datetime2, SUBSTRING(CONVERT(varchar, %s, 20), 0, 14) + ':00:00')" % field_name
189-
elif lookup_type == 'minute':
190-
sql = "CONVERT(datetime2, SUBSTRING(CONVERT(varchar, %s, 20), 0, 17) + ':00')" % field_name
191-
elif lookup_type == 'second':
192-
sql = "CONVERT(datetime2, CONVERT(varchar, %s, 20))" % field_name
193-
return sql
173+
if DJANGO41:
174+
def date_trunc_sql(self, lookup_type, sql, params, tzname=None):
175+
sql, params = self._convert_sql_to_tz(sql, params, tzname)
176+
CONVERT_YEAR = 'CONVERT(varchar, DATEPART(year, %s))' % sql
177+
CONVERT_QUARTER = 'CONVERT(varchar, 1+((DATEPART(quarter, %s)-1)*3))' % sql
178+
CONVERT_MONTH = 'CONVERT(varchar, DATEPART(month, %s))' % sql
179+
CONVERT_WEEK = "DATEADD(DAY, (DATEPART(weekday, %s) + 5) %%%% 7 * -1, %s)" % (sql, sql)
180+
181+
if lookup_type == 'year':
182+
sql = "CONVERT(datetime2, %s + '/01/01')" % CONVERT_YEAR
183+
if lookup_type == 'quarter':
184+
sql = "CONVERT(datetime2, %s + '/' + %s + '/01')" % (CONVERT_YEAR, CONVERT_QUARTER)
185+
if lookup_type == 'month':
186+
sql = "CONVERT(datetime2, %s + '/' + %s + '/01')" % (CONVERT_YEAR, CONVERT_MONTH)
187+
if lookup_type == 'week':
188+
sql = "CONVERT(datetime2, CONVERT(varchar, %s, 112))" % CONVERT_WEEK
189+
if lookup_type == 'day':
190+
sql = "CONVERT(datetime2, CONVERT(varchar(12), %s, 112))" % sql
191+
return sql, params
192+
else:
193+
def date_trunc_sql(self, lookup_type, field_name, tzname=None):
194+
field_name = self._convert_field_to_tz(field_name, tzname)
195+
CONVERT_YEAR = 'CONVERT(varchar, DATEPART(year, %s))' % field_name
196+
CONVERT_QUARTER = 'CONVERT(varchar, 1+((DATEPART(quarter, %s)-1)*3))' % field_name
197+
CONVERT_MONTH = 'CONVERT(varchar, DATEPART(month, %s))' % field_name
198+
CONVERT_WEEK = "DATEADD(DAY, (DATEPART(weekday, %s) + 5) %%%% 7 * -1, %s)" % (field_name, field_name)
199+
200+
if lookup_type == 'year':
201+
return "CONVERT(datetime2, %s + '/01/01')" % CONVERT_YEAR
202+
if lookup_type == 'quarter':
203+
return "CONVERT(datetime2, %s + '/' + %s + '/01')" % (CONVERT_YEAR, CONVERT_QUARTER)
204+
if lookup_type == 'month':
205+
return "CONVERT(datetime2, %s + '/' + %s + '/01')" % (CONVERT_YEAR, CONVERT_MONTH)
206+
if lookup_type == 'week':
207+
return "CONVERT(datetime2, CONVERT(varchar, %s, 112))" % CONVERT_WEEK
208+
if lookup_type == 'day':
209+
return "CONVERT(datetime2, CONVERT(varchar(12), %s, 112))" % field_name
210+
211+
if DJANGO41:
212+
def datetime_cast_date_sql(self, sql, params, tzname):
213+
sql, params = self._convert_sql_to_tz(sql, params, tzname)
214+
sql = 'CAST(%s AS date)' % sql
215+
return sql, params
216+
else:
217+
def datetime_cast_date_sql(self, field_name, tzname):
218+
field_name = self._convert_field_to_tz(field_name, tzname)
219+
sql = 'CAST(%s AS date)' % field_name
220+
return sql
221+
222+
if DJANGO41:
223+
def datetime_cast_time_sql(self, sql, params, tzname):
224+
sql, params = self._convert_sql_to_tz(sql, params, tzname)
225+
sql = 'CAST(%s AS time)' % sql
226+
return sql, params
227+
else:
228+
def datetime_cast_time_sql(self, field_name, tzname):
229+
field_name = self._convert_field_to_tz(field_name, tzname)
230+
sql = 'CAST(%s AS time)' % field_name
231+
return sql
232+
233+
if DJANGO41:
234+
def datetime_extract_sql(self, lookup_type, sql, params, tzname):
235+
sql, params = self._convert_sql_to_tz(sql, params, tzname)
236+
return self.date_extract_sql(lookup_type, sql, params)
237+
else:
238+
def datetime_extract_sql(self, lookup_type, field_name, tzname):
239+
field_name = self._convert_field_to_tz(field_name, tzname)
240+
return self.date_extract_sql(lookup_type, field_name)
241+
242+
if DJANGO41:
243+
def datetime_trunc_sql(self, lookup_type, sql, params, tzname):
244+
sql, params = self._convert_sql_to_tz(sql, params, tzname)
245+
if lookup_type in ('year', 'quarter', 'month', 'week', 'day'):
246+
return self.date_trunc_sql(lookup_type, sql, params)
247+
elif lookup_type == 'hour':
248+
sql = "CONVERT(datetime2, SUBSTRING(CONVERT(varchar, %s, 20), 0, 14) + ':00:00')" % sql
249+
elif lookup_type == 'minute':
250+
sql = "CONVERT(datetime2, SUBSTRING(CONVERT(varchar, %s, 20), 0, 17) + ':00')" % sql
251+
elif lookup_type == 'second':
252+
sql = "CONVERT(datetime2, CONVERT(varchar, %s, 20))" % sql
253+
return sql, params
254+
else:
255+
def datetime_trunc_sql(self, lookup_type, field_name, tzname):
256+
field_name = self._convert_field_to_tz(field_name, tzname)
257+
sql = ''
258+
if lookup_type in ('year', 'quarter', 'month', 'week', 'day'):
259+
sql = self.date_trunc_sql(lookup_type, field_name)
260+
elif lookup_type == 'hour':
261+
sql = "CONVERT(datetime2, SUBSTRING(CONVERT(varchar, %s, 20), 0, 14) + ':00:00')" % field_name
262+
elif lookup_type == 'minute':
263+
sql = "CONVERT(datetime2, SUBSTRING(CONVERT(varchar, %s, 20), 0, 17) + ':00')" % field_name
264+
elif lookup_type == 'second':
265+
sql = "CONVERT(datetime2, CONVERT(varchar, %s, 20))" % field_name
266+
return sql
194267

195268
def fetch_returned_insert_rows(self, cursor):
196269
"""
@@ -494,29 +567,46 @@ def adapt_datetimefield_value(self, value):
494567
# When support for time zones is enabled, Django stores datetime information
495568
# in UTC in the database and uses time-zone-aware objects internally
496569
# source: https://docs.djangoproject.com/en/dev/topics/i18n/timezones/#overview
497-
value = value.astimezone(timezone.utc)
570+
value = value.astimezone(datetime.timezone.utc)
498571
else:
499572
# When USE_TZ is False, settings.TIME_ZONE is the time zone in
500573
# which Django will store all datetimes
501574
# source: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TIME_ZONE
502575
value = timezone.make_naive(value, self.connection.timezone)
503576
return value
504577

505-
def time_trunc_sql(self, lookup_type, field_name, tzname=''):
506-
# if self.connection.sql_server_version >= 2012:
507-
# fields = {
508-
# 'hour': 'DATEPART(hour, %s)' % field_name,
509-
# 'minute': 'DATEPART(minute, %s)' % field_name if lookup_type != 'hour' else '0',
510-
# 'second': 'DATEPART(second, %s)' % field_name if lookup_type == 'second' else '0',
511-
# }
512-
# sql = 'TIMEFROMPARTS(%(hour)s, %(minute)s, %(second)s, 0, 0)' % fields
513-
if lookup_type == 'hour':
514-
sql = "CONVERT(time, SUBSTRING(CONVERT(varchar, %s, 114), 0, 3) + ':00:00')" % field_name
515-
elif lookup_type == 'minute':
516-
sql = "CONVERT(time, SUBSTRING(CONVERT(varchar, %s, 114), 0, 6) + ':00')" % field_name
517-
elif lookup_type == 'second':
518-
sql = "CONVERT(time, SUBSTRING(CONVERT(varchar, %s, 114), 0, 9))" % field_name
519-
return sql
578+
if DJANGO41:
579+
def time_trunc_sql(self, lookup_type, sql, params, tzname=None):
580+
# if self.connection.sql_server_version >= 2012:
581+
# fields = {
582+
# 'hour': 'DATEPART(hour, %s)' % field_name,
583+
# 'minute': 'DATEPART(minute, %s)' % field_name if lookup_type != 'hour' else '0',
584+
# 'second': 'DATEPART(second, %s)' % field_name if lookup_type == 'second' else '0',
585+
# }
586+
# sql = 'TIMEFROMPARTS(%(hour)s, %(minute)s, %(second)s, 0, 0)' % fields
587+
if lookup_type == 'hour':
588+
sql = "CONVERT(time, SUBSTRING(CONVERT(varchar, %s, 114), 0, 3) + ':00:00')" % sql
589+
elif lookup_type == 'minute':
590+
sql = "CONVERT(time, SUBSTRING(CONVERT(varchar, %s, 114), 0, 6) + ':00')" % sql
591+
elif lookup_type == 'second':
592+
sql = "CONVERT(time, SUBSTRING(CONVERT(varchar, %s, 114), 0, 9))" % sql
593+
return sql, params
594+
else:
595+
def time_trunc_sql(self, lookup_type, field_name, tzname=''):
596+
# if self.connection.sql_server_version >= 2012:
597+
# fields = {
598+
# 'hour': 'DATEPART(hour, %s)' % field_name,
599+
# 'minute': 'DATEPART(minute, %s)' % field_name if lookup_type != 'hour' else '0',
600+
# 'second': 'DATEPART(second, %s)' % field_name if lookup_type == 'second' else '0',
601+
# }
602+
# sql = 'TIMEFROMPARTS(%(hour)s, %(minute)s, %(second)s, 0, 0)' % fields
603+
if lookup_type == 'hour':
604+
sql = "CONVERT(time, SUBSTRING(CONVERT(varchar, %s, 114), 0, 3) + ':00:00')" % field_name
605+
elif lookup_type == 'minute':
606+
sql = "CONVERT(time, SUBSTRING(CONVERT(varchar, %s, 114), 0, 6) + ':00')" % field_name
607+
elif lookup_type == 'second':
608+
sql = "CONVERT(time, SUBSTRING(CONVERT(varchar, %s, 114), 0, 9))" % field_name
609+
return sql
520610

521611
def conditional_expression_supported_in_where_clause(self, expression):
522612
"""

0 commit comments

Comments
 (0)