diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 45ac49841..168bccf83 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -275,6 +275,14 @@ except ImportError: def pygments_css(style): return None + +try: + import pytz + from pytz.exceptions import InvalidTimeError +except ImportError: + InvalidTimeError = Exception + + # `separators` argument to `json.dumps()` differs between 2.x and 3.x # See: http://bugs.python.org/issue22767 if six.PY3: @@ -339,6 +347,7 @@ def set_many(instance, field, value): field = getattr(instance, field) field.set(value) + def include(module, namespace=None, app_name=None): from django.conf.urls import include if django.VERSION < (1,9): diff --git a/rest_framework/fields.py b/rest_framework/fields.py index bfb24555d..41d6105ca 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -33,7 +33,8 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import ISO_8601 from rest_framework.compat import ( - get_remote_field, unicode_repr, unicode_to_repr, value_from_object + InvalidTimeError, get_remote_field, unicode_repr, unicode_to_repr, + value_from_object ) from rest_framework.exceptions import ErrorDetail, ValidationError from rest_framework.settings import api_settings @@ -1087,6 +1088,7 @@ class DateTimeField(Field): default_error_messages = { 'invalid': _('Datetime has wrong format. Use one of these formats instead: {format}.'), 'date': _('Expected a datetime but got a date.'), + 'make_aware': _('Invalid datetime for the timezone "{timezone}".') } datetime_parser = datetime.datetime.strptime @@ -1107,7 +1109,10 @@ class DateTimeField(Field): field_timezone = getattr(self, 'timezone', self.default_timezone()) if (field_timezone is not None) and not timezone.is_aware(value): - return timezone.make_aware(value, field_timezone) + try: + return timezone.make_aware(value, field_timezone) + except InvalidTimeError: + self.fail('make_aware', timezone=field_timezone) elif (field_timezone is None) and timezone.is_aware(value): return timezone.make_naive(value, utc) return value diff --git a/tests/test_fields.py b/tests/test_fields.py index 16221d4cc..968c41d3f 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -12,7 +12,7 @@ from django.utils import six from django.utils.timezone import utc import rest_framework -from rest_framework import serializers +from rest_framework import compat, serializers from rest_framework.fields import is_simple_callable try: @@ -1205,6 +1205,30 @@ class TestNaiveDateTimeField(FieldValues): field = serializers.DateTimeField(default_timezone=None) +class TestNaiveDayLightSavingTimeTimeZoneDateTimeField(FieldValues): + """ + Invalid values for `DateTimeField` with datetime in DST shift (non-existing or ambiguous) and timezone with DST. + Timezone America/New_York has DST shift from 2017-03-12T02:00:00 to 2017-03-12T03:00:00 and + from 2017-11-05T02:00:00 to 2017-11-05T01:00:00 in 2017. + """ + valid_inputs = {} + invalid_inputs = { + '2017-03-12T02:30:00': ['Invalid datetime for the timezone "America/New_York".'], + '2017-11-05T01:30:00': ['Invalid datetime for the timezone "America/New_York".'] + } + outputs = {} + + class MockTimezone: + @staticmethod + def localize(value, is_dst): + raise compat.InvalidTimeError() + + def __str__(self): + return 'America/New_York' + + field = serializers.DateTimeField(default_timezone=MockTimezone()) + + class TestTimeField(FieldValues): """ Valid and invalid values for `TimeField`.