diff --git a/requirements/requirements-optionals.txt b/requirements/requirements-optionals.txt index e8ba50851..319b7547a 100644 --- a/requirements/requirements-optionals.txt +++ b/requirements/requirements-optionals.txt @@ -1,4 +1,5 @@ # Optional packages which may be used with REST framework. +pytz==2017.2 markdown==2.6.4 django-guardian==1.4.8 django-filter==1.0.4 diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 63df452ce..072bbf1b9 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1125,7 +1125,9 @@ class DateTimeField(Field): """ field_timezone = getattr(self, 'timezone', self.default_timezone()) - if (field_timezone is not None) and not timezone.is_aware(value): + if field_timezone is not None: + if timezone.is_aware(value): + return value.astimezone(field_timezone) try: return timezone.make_aware(value, field_timezone) except InvalidTimeError: @@ -1135,7 +1137,7 @@ class DateTimeField(Field): return value def default_timezone(self): - return timezone.get_default_timezone() if settings.USE_TZ else None + return timezone.get_current_timezone() if settings.USE_TZ else None def to_internal_value(self, value): input_formats = getattr(self, 'input_formats', api_settings.DATETIME_INPUT_FORMATS) @@ -1174,6 +1176,7 @@ class DateTimeField(Field): return value if output_format.lower() == ISO_8601: + value = self.enforce_timezone(value) value = value.isoformat() if value.endswith('+00:00'): value = value[:-6] + 'Z' diff --git a/tests/test_fields.py b/tests/test_fields.py index 011fbe7de..c2b053189 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -10,12 +10,17 @@ import pytest from django.http import QueryDict from django.test import TestCase, override_settings from django.utils import six -from django.utils.timezone import utc +from django.utils.timezone import activate, deactivate, utc import rest_framework from rest_framework import compat, serializers from rest_framework.fields import is_simple_callable +try: + import pytz +except ImportError: + pytz = None + try: import typings except ImportError: @@ -1168,7 +1173,7 @@ class TestDateTimeField(FieldValues): datetime.date(2001, 1, 1): ['Expected a datetime but got a date.'], } outputs = { - datetime.datetime(2001, 1, 1, 13, 00): '2001-01-01T13:00:00', + datetime.datetime(2001, 1, 1, 13, 00): '2001-01-01T13:00:00Z', datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc): '2001-01-01T13:00:00Z', '2001-01-01T00:00:00': '2001-01-01T00:00:00', six.text_type('2016-01-10T00:00:00'): '2016-01-10T00:00:00', @@ -1230,10 +1235,59 @@ class TestNaiveDateTimeField(FieldValues): '2001-01-01 13:00': datetime.datetime(2001, 1, 1, 13, 00), } invalid_inputs = {} - outputs = {} + outputs = { + datetime.datetime(2001, 1, 1, 13, 00): '2001-01-01T13:00:00', + datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc): '2001-01-01T13:00:00', + } field = serializers.DateTimeField(default_timezone=None) +@pytest.mark.skipif(pytz is None, reason='pytz not installed') +class TestTZWithDateTimeField(FieldValues): + """ + Valid and invalid values for `DateTimeField` when not using UTC as the timezone. + """ + @classmethod + def setup_class(cls): + # use class setup method, as class-level attribute will still be evaluated even if test is skipped + kolkata = pytz.timezone('Asia/Kolkata') + + cls.valid_inputs = { + '2016-12-19T10:00:00': kolkata.localize(datetime.datetime(2016, 12, 19, 10)), + '2016-12-19T10:00:00+05:30': kolkata.localize(datetime.datetime(2016, 12, 19, 10)), + datetime.datetime(2016, 12, 19, 10): kolkata.localize(datetime.datetime(2016, 12, 19, 10)), + } + cls.invalid_inputs = {} + cls.outputs = { + datetime.datetime(2016, 12, 19, 10): '2016-12-19T10:00:00+05:30', + datetime.datetime(2016, 12, 19, 4, 30, tzinfo=utc): '2016-12-19T10:00:00+05:30', + } + cls.field = serializers.DateTimeField(default_timezone=kolkata) + + +@pytest.mark.skipif(pytz is None, reason='pytz not installed') +@override_settings(TIME_ZONE='UTC', USE_TZ=True) +class TestDefaultTZDateTimeField(TestCase): + """ + Test the current/default timezone handling in `DateTimeField`. + """ + + @classmethod + def setup_class(cls): + cls.field = serializers.DateTimeField() + cls.kolkata = pytz.timezone('Asia/Kolkata') + + def test_default_timezone(self): + assert self.field.default_timezone() == utc + + def test_current_timezone(self): + assert self.field.default_timezone() == utc + activate(self.kolkata) + assert self.field.default_timezone() == self.kolkata + deactivate() + assert self.field.default_timezone() == utc + + class TestNaiveDayLightSavingTimeTimeZoneDateTimeField(FieldValues): """ Invalid values for `DateTimeField` with datetime in DST shift (non-existing or ambiguous) and timezone with DST.