This commit is contained in:
Dale Hui 2017-01-10 23:06:48 +00:00 committed by GitHub
commit cfb21f4ae7
6 changed files with 102 additions and 11 deletions

View File

@ -299,7 +299,7 @@ Corresponds to `django.db.models.fields.DateTimeField`.
#### `DateTimeField` format strings.
Format strings may either be [Python strftime formats][strftime] which explicitly specify the format, or the special string `'iso-8601'`, which indicates that [ISO 8601][iso8601] style datetimes should be used. (eg `'2013-01-29T12:34:56.000000Z'`)
Format strings may either be [Python strftime formats][strftime] which explicitly specify the format, or the special strings `'iso-8601'`, which indicates that [ISO 8601][iso8601] style datetimes should be used (eg `'2013-01-29T12:34:56.000000Z'`), and `'iso-8601-strict'`, which indicates that [ISO 8601][iso8601] style datetimes without fraction seconds should be used (eg `'2013-01-29T12:34:56Z'`)
When a value of `None` is used for the format `datetime` objects will be returned by `to_representation` and the final output representation will determined by the renderer class.
@ -345,7 +345,7 @@ Corresponds to `django.db.models.fields.TimeField`
#### `TimeField` format strings
Format strings may either be [Python strftime formats][strftime] which explicitly specify the format, or the special string `'iso-8601'`, which indicates that [ISO 8601][iso8601] style times should be used. (eg `'12:34:56.000000'`)
Format strings may either be [Python strftime formats][strftime] which explicitly specify the format, or the special strings `'iso-8601'`, which indicates that [ISO 8601][iso8601] style times should be used (eg `'12:34:56.000000'`), and `'iso-8601-strict'`, which indicates that [ISO 8601][iso8601] style times without fractional seconds should be used (eg `'12:34:56'`).
## DurationField

View File

@ -286,7 +286,7 @@ Default: `'format'`
A format string that should be used by default for rendering the output of `DateTimeField` serializer fields. If `None`, then `DateTimeField` serializer fields will return Python `datetime` objects, and the datetime encoding will be determined by the renderer.
May be any of `None`, `'iso-8601'` or a Python [strftime format][strftime] string.
May be any of `None`, `'iso-8601'`, `'iso-8601-strict'`, or a Python [strftime format][strftime] string.
Default: `'iso-8601'`
@ -294,7 +294,7 @@ Default: `'iso-8601'`
A list of format strings that should be used by default for parsing inputs to `DateTimeField` serializer fields.
May be a list including the string `'iso-8601'` or Python [strftime format][strftime] strings.
May be a list including the string `'iso-8601'`, `'iso-8601-strict'` or Python [strftime format][strftime] strings.
Default: `['iso-8601']`
@ -318,7 +318,7 @@ Default: `['iso-8601']`
A format string that should be used by default for rendering the output of `TimeField` serializer fields. If `None`, then `TimeField` serializer fields will return Python `time` objects, and the time encoding will be determined by the renderer.
May be any of `None`, `'iso-8601'` or a Python [strftime format][strftime] string.
May be any of `None`, `'iso-8601'`, `'iso-8601-strict'` or a Python [strftime format][strftime] string.
Default: `'iso-8601'`
@ -326,7 +326,7 @@ Default: `'iso-8601'`
A list of format strings that should be used by default for parsing inputs to `TimeField` serializer fields.
May be a list including the string `'iso-8601'` or Python [strftime format][strftime] strings.
May be a list including the string `'iso-8601'`, `'iso-8601-strict'` or Python [strftime format][strftime] strings.
Default: `['iso-8601']`

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.