Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions mssql/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,17 @@ def max_in_list_size(self):

def _convert_field_to_tz(self, field_name, tzname):
if tzname and settings.USE_TZ and self.connection.timezone_name != tzname:
offset = self._get_utcoffset(tzname)
target_offset = self._get_utcoffset(tzname)
connection_offset = self._get_utcoffset(self.connection.timezone_name)
offset = target_offset - connection_offset
field_name = 'DATEADD(second, %d, %s)' % (offset, field_name)
return field_name

def _convert_sql_to_tz(self, sql, params, tzname):
if tzname and settings.USE_TZ and self.connection.timezone_name != tzname:
offset = self._get_utcoffset(tzname)
target_offset = self._get_utcoffset(tzname)
connection_offset = self._get_utcoffset(self.connection.timezone_name)
offset = target_offset - connection_offset
sql = 'DATEADD(second, %d, %s)' % (offset, sql)
return sql, params

Expand Down Expand Up @@ -614,10 +618,11 @@ def adapt_datetimefield_value(self, value):

if timezone.is_aware(value):
if settings.USE_TZ:
# When support for time zones is enabled, Django stores datetime information
# in UTC in the database and uses time-zone-aware objects internally
# source: https://docs.djangoproject.com/en/dev/topics/i18n/timezones/#overview
value = value.astimezone(datetime.timezone.utc)
# SQL Server's datetime2 type doesn't preserve time zone
# information. Store aware values as naive datetimes in the
# connection's configured time zone so the read and write paths
# use the same convention.
value = timezone.make_naive(value, self.connection.timezone)
else:
# When USE_TZ is False, settings.TIME_ZONE is the time zone in
# which Django will store all datetimes
Expand Down
62 changes: 62 additions & 0 deletions testapp/tests/test_timezones.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,33 @@
# Licensed under the BSD license.

import datetime
from contextlib import contextmanager

from django.db import connection
from django.test import TestCase
from django.test.utils import override_settings

from ..models import TimeZone


@contextmanager
def override_database_connection_timezone(timezone):
original_timezone = connection.settings_dict['TIME_ZONE']
try:
connection.settings_dict['TIME_ZONE'] = timezone
connection.timezone
del connection.timezone
connection.timezone_name
del connection.timezone_name
yield
finally:
connection.settings_dict['TIME_ZONE'] = original_timezone
connection.timezone
del connection.timezone
connection.timezone_name
del connection.timezone_name


class TestDateTimeField(TestCase):

def test_iso_week_day(self):
Expand All @@ -23,6 +44,47 @@ def test_iso_week_day(self):
for k, v in days.items():
self.assertSequenceEqual(TimeZone.objects.filter(date__iso_week_day=k), [v])

@override_settings(USE_TZ=True)
def test_adapt_datetimefield_value_uses_database_timezone(self):
value = datetime.datetime(2024, 1, 15, 12, tzinfo=datetime.timezone.utc)

with override_database_connection_timezone('Europe/Berlin'):
adapted = connection.ops.adapt_datetimefield_value(value)

self.assertEqual(adapted, datetime.datetime(2024, 1, 15, 13))
self.assertIsNone(adapted.tzinfo)

@override_settings(USE_TZ=True)
def test_datetime_sql_conversion_uses_database_timezone(self):
with override_database_connection_timezone('Asia/Bangkok'):
converted_field = connection.ops._convert_field_to_tz(
'[date]', 'Africa/Nairobi'
)
converted_sql, params = connection.ops._convert_sql_to_tz(
'[date]', [], 'Africa/Nairobi'
)

expected = 'DATEADD(second, -14400, [date])'
self.assertEqual(converted_field, expected)
self.assertEqual(converted_sql, expected)
self.assertEqual(params, [])

@override_settings(USE_TZ=True, TIME_ZONE='Africa/Nairobi')
def test_datetime_round_trip_uses_database_timezone(self):
value = datetime.datetime(2024, 7, 15, 20, 10, tzinfo=datetime.timezone.utc)
database_timezone = datetime.timezone(datetime.timedelta(hours=7))

with override_database_connection_timezone('Asia/Bangkok'):
obj = TimeZone.objects.create(date=value)
retrieved = TimeZone.objects.get(pk=obj.pk)
by_exact_datetime = TimeZone.objects.get(date=value)
by_local_date = TimeZone.objects.get(date__date=datetime.date(2024, 7, 15))

self.assertEqual(retrieved.date, value.astimezone(database_timezone))
self.assertEqual(by_exact_datetime, obj)
self.assertEqual(by_local_date, obj)


class TestDateTimeToDateTimeOffsetMigration(TestCase):

def setUp(self):
Expand Down