Support parsing strict ISO 8601 input format for DateTimeField and TimeField causing microseconds to be ignored

This commit is contained in:
Dale Hui 2016-12-05 08:28:51 -08:00
parent 45e90c3398
commit 87440e73a1
4 changed files with 96 additions and 5 deletions

View File

@ -21,3 +21,4 @@ HTTP_HEADER_ENCODING = 'iso-8859-1'
# Default datetime input and output formats
ISO_8601 = 'iso-8601'
ISO_8601_STRICT = 'iso-8601-strict'

View File

@ -30,7 +30,7 @@ from django.utils.functional import cached_property
from django.utils.ipv6 import clean_ipv6_address
from django.utils.translation import ugettext_lazy as _
from rest_framework import ISO_8601
from rest_framework import ISO_8601, ISO_8601_STRICT
from rest_framework.compat import (
get_remote_field, unicode_repr, unicode_to_repr, value_from_object
)
@ -1120,13 +1120,15 @@ class DateTimeField(Field):
return self.enforce_timezone(value)
for input_format in input_formats:
if input_format.lower() == ISO_8601:
if input_format.lower() in (ISO_8601, ISO_8601_STRICT):
try:
parsed = parse_datetime(value)
except (ValueError, TypeError):
pass
else:
if parsed is not None:
if input_format == ISO_8601_STRICT:
parsed = parsed - datetime.timedelta(microseconds=parsed.microsecond)
return self.enforce_timezone(parsed)
else:
try:
@ -1153,6 +1155,10 @@ class DateTimeField(Field):
if value.endswith('+00:00'):
value = value[:-6] + 'Z'
return value
if output_format.lower() == ISO_8601_STRICT:
value = value - datetime.timedelta(microseconds=value.microsecond)
return value.isoformat()
return value.strftime(output_format)
@ -1243,13 +1249,15 @@ class TimeField(Field):
return value
for input_format in input_formats:
if input_format.lower() == ISO_8601:
if input_format.lower() in (ISO_8601, ISO_8601_STRICT):
try:
parsed = parse_time(value)
except (ValueError, TypeError):
pass
else:
if parsed is not None:
if input_format.lower() == ISO_8601_STRICT:
return parsed.replace(microsecond=0)
return parsed
else:
try:
@ -1282,6 +1290,10 @@ class TimeField(Field):
if output_format.lower() == ISO_8601:
return value.isoformat()
if output_format.lower() == ISO_8601_STRICT:
if value.microsecond:
value = value.replace(microsecond=0)
return value.isoformat()
return value.strftime(output_format)

View File

@ -1,11 +1,14 @@
"""
Helper functions that convert strftime formats into more readable representations.
"""
from rest_framework import ISO_8601
from rest_framework import ISO_8601, ISO_8601_STRICT
def datetime_formats(formats):
format = ', '.join(formats).replace(
ISO_8601_STRICT,
'YYYY-MM-DDThh:mm[:ss][+HH:MM|-HH:MM|Z]'
).replace(
ISO_8601,
'YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z]'
)
@ -18,7 +21,8 @@ def date_formats(formats):
def time_formats(formats):
format = ', '.join(formats).replace(ISO_8601, 'hh:mm[:ss[.uuuuuu]]')
format = ', '.join(formats).replace(ISO_8601_STRICT, 'hh:mm[:ss]') \
.replace(ISO_8601, 'hh:mm[:ss[.uuuuuu]]')
return humanize_strptime(format)

View File

@ -1130,10 +1130,15 @@ class TestDateTimeField(FieldValues):
"""
valid_inputs = {
'2001-01-01 13:00': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()),
'2001-01-01 13:00:00.123456': datetime.datetime(2001, 1, 1, 13, 00, 00, 123456, tzinfo=timezone.UTC()),
'2001-01-01T13:00': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()),
'2001-01-01T13:00:00.123456': datetime.datetime(2001, 1, 1, 13, 00, 00, 123456, tzinfo=timezone.UTC()),
'2001-01-01T13:00Z': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()),
'2001-01-01T13:00:00.123456Z': datetime.datetime(2001, 1, 1, 13, 00, 00, 123456, tzinfo=timezone.UTC()),
datetime.datetime(2001, 1, 1, 13, 00): datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()),
datetime.datetime(2001, 1, 1, 13, 00, 00, 123456): datetime.datetime(2001, 1, 1, 13, 00, 00, 123456, tzinfo=timezone.UTC()),
datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()): datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()),
datetime.datetime(2001, 1, 1, 13, 00, 00, 123456, tzinfo=timezone.UTC()): datetime.datetime(2001, 1, 1, 13, 00, 00, 123456, tzinfo=timezone.UTC()),
# Django 1.4 does not support timezone string parsing.
'2001-01-01T13:00Z': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC())
}
@ -1144,7 +1149,9 @@ class TestDateTimeField(FieldValues):
}
outputs = {
datetime.datetime(2001, 1, 1, 13, 00): '2001-01-01T13:00:00',
datetime.datetime(2001, 1, 1, 13, 00, 00, 123456): '2001-01-01T13:00:00.123456',
datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()): '2001-01-01T13:00:00Z',
datetime.datetime(2001, 1, 1, 13, 00, 00, 123456, tzinfo=timezone.UTC()): '2001-01-01T13:00:00.123456Z',
'2001-01-01T00:00:00': '2001-01-01T00:00:00',
six.text_type('2016-01-10T00:00:00'): '2016-01-10T00:00:00',
None: None,
@ -1153,6 +1160,41 @@ class TestDateTimeField(FieldValues):
field = serializers.DateTimeField(default_timezone=timezone.UTC())
class TestIso8601StrictDateTimeField(FieldValues):
valid_inputs = {
'2001-01-01 13:00': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()),
'2001-01-01 13:00:00.123456': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()),
'2001-01-01T13:00': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()),
'2001-01-01T13:00:00.123456': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()),
'2001-01-01T13:00Z': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()),
'2001-01-01T13:00:00.123456Z': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()),
datetime.datetime(2001, 1, 1, 13, 00): datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()),
datetime.datetime(2001, 1, 1, 13, 00, 00, 123456): datetime.datetime(2001, 1, 1, 13, 00, 00, 123456, tzinfo=timezone.UTC()),
datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()): datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()),
datetime.datetime(2001, 1, 1, 13, 00, 00, 123456, tzinfo=timezone.UTC()): datetime.datetime(2001, 1, 1, 13, 00, 00, 123456, tzinfo=timezone.UTC()),
# Django 1.4 does not support timezone string parsing.
'2001-01-01T13:00Z': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC())
}
invalid_inputs = {
'abc': ['Datetime has wrong format. Use one of these formats instead: YYYY-MM-DDThh:mm[:ss][+HH:MM|-HH:MM|Z].'],
'2001-99-99T99:00': ['Datetime has wrong format. Use one of these formats instead: YYYY-MM-DDThh:mm[:ss][+HH:MM|-HH:MM|Z].'],
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, 00, 123456): '2001-01-01T13:00:00',
datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()): '2001-01-01T13:00:00+00:00',
datetime.datetime(2001, 1, 1, 13, 00, 00, 123456, tzinfo=timezone.UTC()): '2001-01-01T13:00:00+00:00',
'2001-01-01T00:00:00': '2001-01-01T00:00:00',
six.text_type('2016-01-10T00:00:00'): '2016-01-10T00:00:00',
None: None,
'': None,
}
field = serializers.DateTimeField(format=rest_framework.ISO_8601_STRICT, input_formats=[rest_framework.ISO_8601_STRICT],
default_timezone=timezone.UTC())
class TestCustomInputFormatDateTimeField(FieldValues):
"""
Valid and invalid values for `DateTimeField` with a custom input format.
@ -1210,7 +1252,9 @@ class TestTimeField(FieldValues):
"""
valid_inputs = {
'13:00': datetime.time(13, 00),
'13:00:00.123456': datetime.time(13, 00, 00, 123456),
datetime.time(13, 00): datetime.time(13, 00),
datetime.time(13, 00, 00, 123456): datetime.time(13, 00, 00, 123456),
}
invalid_inputs = {
'abc': ['Time has wrong format. Use one of these formats instead: hh:mm[:ss[.uuuuuu]].'],
@ -1218,14 +1262,44 @@ class TestTimeField(FieldValues):
}
outputs = {
datetime.time(13, 0): '13:00:00',
datetime.time(13, 0, 0, 123456): '13:00:00.123456',
datetime.time(0, 0): '00:00:00',
datetime.time(0, 0, 0, 0): '00:00:00',
'00:00:00': '00:00:00',
'00:00:00.000000': '00:00:00.000000',
None: None,
'': None,
}
field = serializers.TimeField()
class TestIso8601StrictTimeField(FieldValues):
"""
Valid and invalid values for `TimeField`.
"""
valid_inputs = {
'13:00': datetime.time(13, 00),
'13:00:00.123456': datetime.time(13, 00),
datetime.time(13, 00): datetime.time(13, 00),
datetime.time(13, 00, 00, 123456): datetime.time(13, 00, 00, 123456),
}
invalid_inputs = {
'abc': ['Time has wrong format. Use one of these formats instead: hh:mm[:ss].'],
'99:99': ['Time has wrong format. Use one of these formats instead: hh:mm[:ss].'],
}
outputs = {
datetime.time(13, 0): '13:00:00',
datetime.time(13, 0, 0, 123456): '13:00:00',
datetime.time(0, 0): '00:00:00',
datetime.time(0, 0, 0, 0): '00:00:00',
'00:00:00': '00:00:00',
'00:00:00.000000': '00:00:00.000000',
None: None,
'': None,
}
field = serializers.TimeField(format=rest_framework.ISO_8601_STRICT, input_formats=[rest_framework.ISO_8601_STRICT])
class TestCustomInputFormatTimeField(FieldValues):
"""
Valid and invalid values for `TimeField` with a custom input format.