Skip to content

Commit 9d506ba

Browse files
committed
Allow format duration as ISO-8601
1 parent 8e81d38 commit 9d506ba

File tree

5 files changed

+39
-4
lines changed

5 files changed

+39
-4
lines changed

docs/api-guide/fields.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ The `validated_data` for these fields will contain a `datetime.timedelta` instan
396396
- `min_value` Validate that the duration provided is no less than this value.
397397

398398
#### `DurationField` format strings
399-
Format strings may either be the special string `'iso-8601'`, which indicates that [ISO 8601][iso8601] style intervals should be used (eg `'P4DT1H15M20S'`), or the special string `'standard'`, which indicates that Django interval format `'[DD] [HH:[MM:]]ss[.uuuuuu]'` hould be used (eg: `'4 1:15:20'`).
399+
Format strings may either be the special string `'iso-8601'`, which indicates that [ISO 8601][iso8601] style intervals should be used (eg `'P4DT1H15M20S'`), or the special string `'standard'`, which indicates that Django interval format `'[DD] [HH:[MM:]]ss[.uuuuuu]'` should be used (eg: `'4 1:15:20'`).
400400

401401
---
402402

rest_framework/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
# Default datetime input and output formats
2525
ISO_8601 = 'iso-8601'
26+
STD_DURATION = 'standard'
2627

2728

2829
if django.VERSION < (3, 2):

rest_framework/fields.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from django.utils.dateparse import (
2424
parse_date, parse_datetime, parse_duration, parse_time
2525
)
26-
from django.utils.duration import duration_string
26+
from django.utils.duration import duration_iso_string, duration_string
2727
from django.utils.encoding import is_protected_type, smart_str
2828
from django.utils.formats import localize_input, sanitize_separators
2929
from django.utils.ipv6 import clean_ipv6_address
@@ -1380,9 +1380,11 @@ class DurationField(Field):
13801380
'min_value': _('Ensure this value is greater than or equal to {min_value}.'),
13811381
}
13821382

1383-
def __init__(self, **kwargs):
1383+
def __init__(self, format=empty, **kwargs):
13841384
self.max_value = kwargs.pop('max_value', None)
13851385
self.min_value = kwargs.pop('min_value', None)
1386+
if format is not empty:
1387+
self.format = format
13861388
super().__init__(**kwargs)
13871389
if self.max_value is not None:
13881390
message = lazy_format(self.error_messages['max_value'], max_value=self.max_value)
@@ -1402,6 +1404,13 @@ def to_internal_value(self, value):
14021404
self.fail('invalid', format='[DD] [HH:[MM:]]ss[.uuuuuu]')
14031405

14041406
def to_representation(self, value):
1407+
output_format = getattr(self, 'format', api_settings.DURATION_FORMAT)
1408+
1409+
if output_format is None or isinstance(value, str):
1410+
return value
1411+
1412+
if output_format.lower() == ISO_8601:
1413+
return duration_iso_string(value)
14051414
return duration_string(value)
14061415

14071416

rest_framework/settings.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from django.test.signals import setting_changed
2323
from django.utils.module_loading import import_string
2424

25-
from rest_framework import ISO_8601
25+
from rest_framework import ISO_8601, STD_DURATION
2626

2727
DEFAULTS = {
2828
# Base API policies
@@ -107,6 +107,8 @@
107107
'TIME_FORMAT': ISO_8601,
108108
'TIME_INPUT_FORMATS': [ISO_8601],
109109

110+
'DURATION_FORMAT': STD_DURATION,
111+
110112
# Encoding
111113
'UNICODE_JSON': True,
112114
'COMPACT_JSON': True,

tests/test_fields.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1633,6 +1633,29 @@ class TestDurationField(FieldValues):
16331633
field = serializers.DurationField()
16341634

16351635

1636+
class TestNoOutputFormatDurationField(FieldValues):
1637+
"""
1638+
Values for `TimeField` with a no output format.
1639+
"""
1640+
valid_inputs = {}
1641+
invalid_inputs = {}
1642+
outputs = {
1643+
datetime.timedelta(1): datetime.timedelta(1)
1644+
}
1645+
field = serializers.DurationField(format=None)
1646+
1647+
1648+
class TestISOOutputFormatDurationField(FieldValues):
1649+
"""
1650+
Values for `TimeField` with a custom output format.
1651+
"""
1652+
valid_inputs = {}
1653+
invalid_inputs = {}
1654+
outputs = {
1655+
datetime.timedelta(days=3, hours=8, minutes=32, seconds=1, microseconds=123) : 'P3DT8H32M1S123MS'
1656+
}
1657+
field = serializers.TimeField(format='is0-8601')
1658+
16361659
# Choice types...
16371660

16381661
class TestChoiceField(FieldValues):

0 commit comments

Comments
 (0)