mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-05 13:00:12 +03:00
Support parsing strict ISO 8601 input format for DateTimeField and TimeField causing microseconds to be ignored
This commit is contained in:
parent
45e90c3398
commit
87440e73a1
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue
Block a user