Support serializing date/datetime values before 1900 on python 2

Python 2 does not support strftime() for datetimes prior to 1900. This
is fixed in Python 3.x but the python developers have signaled that they
do not intend to backport the fix. The module django.utils.datetime_safe
was written to workaround this issue. I've tried to use a light hand,
only using the datetime_safe functions where serialization would
otherwise throw a ValueError.
This commit is contained in:
Frankie Dintino 2018-03-21 14:49:17 -04:00
parent 812d3478bd
commit fce3b59953
No known key found for this signature in database
GPG Key ID: 97E295AACFBABD9E
2 changed files with 54 additions and 11 deletions

View File

@ -18,7 +18,7 @@ from django.core.validators import (
)
from django.forms import FilePathField as DjangoFilePathField
from django.forms import ImageField as DjangoImageField
from django.utils import six, timezone
from django.utils import datetime_safe, six, timezone
from django.utils.dateparse import (
parse_date, parse_datetime, parse_duration, parse_time
)
@ -1152,6 +1152,12 @@ class DateTimeField(Field):
self.timezone = default_timezone
super(DateTimeField, self).__init__(*args, **kwargs)
def ensure_pre_1900_safe(self, value):
if isinstance(value, datetime.date):
if six.PY2 and value.year < 1900:
value = datetime_safe.new_datetime(value)
return value
def enforce_timezone(self, value):
"""
When `self.default_timezone` is `None`, always return naive datetimes.
@ -1162,16 +1168,18 @@ class DateTimeField(Field):
if field_timezone is not None:
if timezone.is_aware(value):
try:
return value.astimezone(field_timezone)
value = value.astimezone(field_timezone)
except OverflowError:
self.fail('overflow')
try:
return timezone.make_aware(value, field_timezone)
except InvalidTimeError:
self.fail('make_aware', timezone=field_timezone)
else:
try:
value = 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
value = timezone.make_naive(value, utc)
return self.ensure_pre_1900_safe(value)
def default_timezone(self):
return timezone.get_current_timezone() if settings.USE_TZ else None
@ -1236,6 +1244,12 @@ class DateField(Field):
self.input_formats = input_formats
super(DateField, self).__init__(*args, **kwargs)
def ensure_pre_1900_safe(self, value):
if isinstance(value, datetime.date):
if six.PY2 and value.year < 1900:
value = datetime_safe.new_date(value)
return value
def to_internal_value(self, value):
input_formats = getattr(self, 'input_formats', api_settings.DATE_INPUT_FORMATS)
@ -1243,7 +1257,7 @@ class DateField(Field):
self.fail('datetime')
if isinstance(value, datetime.date):
return value
return self.ensure_pre_1900_safe(value)
for input_format in input_formats:
if input_format.lower() == ISO_8601:
@ -1253,14 +1267,14 @@ class DateField(Field):
pass
else:
if parsed is not None:
return parsed
return self.ensure_pre_1900_safe(parsed)
else:
try:
parsed = self.datetime_parser(value, input_format)
except (ValueError, TypeError):
pass
else:
return parsed.date()
return self.ensure_pre_1900_safe(parsed.date())
humanized_format = humanize_datetime.date_formats(input_formats)
self.fail('invalid', format=humanized_format)
@ -1283,6 +1297,8 @@ class DateField(Field):
'read-only field and deal with timezone issues explicitly.'
)
value = self.ensure_pre_1900_safe(value)
if output_format.lower() == ISO_8601:
return value.isoformat()

View File

@ -1120,6 +1120,7 @@ class TestDateField(FieldValues):
valid_inputs = {
'2001-01-01': datetime.date(2001, 1, 1),
datetime.date(2001, 1, 1): datetime.date(2001, 1, 1),
'1800-01-01': datetime.date(1800, 1, 1),
}
invalid_inputs = {
'abc': ['Date has wrong format. Use one of these formats instead: YYYY[-MM[-DD]].'],
@ -1129,6 +1130,8 @@ class TestDateField(FieldValues):
outputs = {
datetime.date(2001, 1, 1): '2001-01-01',
'2001-01-01': '2001-01-01',
datetime.date(1800, 1, 1): '1800-01-01',
'1800-01-01': '1800-01-01',
six.text_type('2016-01-10'): '2016-01-10',
None: None,
'': None,
@ -1174,6 +1177,18 @@ class TestNoOutputFormatDateField(FieldValues):
field = serializers.DateField(format=None)
class TestPre1900DateField(FieldValues):
"""
Values for `DateField` prior to 1900
"""
valid_inputs = {}
invalid_inputs = {}
outputs = {
datetime.date(1800, 1, 1): '01 Jan 1800',
}
field = serializers.DateField(format='%d %b %Y')
class TestDateTimeField(FieldValues):
"""
Valid and invalid values for `DateTimeField`.
@ -1348,6 +1363,18 @@ class TestNaiveDayLightSavingTimeTimeZoneDateTimeField(FieldValues):
field = serializers.DateTimeField(default_timezone=MockTimezone())
class TestPre1900DateTimeField(FieldValues):
"""
Values for `DateTimeField` prior to 1900.
"""
valid_inputs = {}
invalid_inputs = {}
outputs = {
datetime.datetime(1800, 1, 1, 13, 00): '01:00PM, 01 Jan 1800',
}
field = serializers.DateTimeField(format='%I:%M%p, %d %b %Y')
class TestTimeField(FieldValues):
"""
Valid and invalid values for `TimeField`.