Skip to content

DurationField output format #8532

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open

Conversation

sevdog
Copy link
Contributor

@sevdog sevdog commented Jun 24, 2022

Description

As explained in #8527 this PR will provide the ability to choose the output format for DurationFields.

Since Python builtim timedelta object does not have any format function we are going to allow only formats which are already defined in Django codebase in the package django.utils.duration:

  • duration_string
  • duration_iso_string

- `max_value` Validate that the duration provided is no greater than this value.
- `min_value` Validate that the duration provided is no less than this value.

#### `DurationField` format strings
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'`).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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'`).
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'`).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the typo 😅

I have fixed it locally, will update as soon as I have other elements to push because I would like to keep the history clear without having to perform too many rebase.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. (Incidentally, all pull requests are squashed, so that doesn't matter at all from my perspective.)


#### DURATION_FORMAT

A format string that should be used by default for rendering the output of `DurationField` serializer fields. If `None`, then `DurationField` serializer fields will return Python `timedelta` objects, and the duration encoding will be determined by the renderer.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If None, then DurationField serializer fields will return Python timedelta objects, and the duration encoding will be determined by the renderer.

What output style does this produce with the current JSONRenderer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the current JSONRenderer, which uses JSONEncoder it will output the string value of "total seconds" (doc).

elif isinstance(obj, datetime.timedelta):
return str(obj.total_seconds())

Using the same example value will be:

from datetime import timedelta
d = timedelta(days=4, hours=1, minutes=15, seconds=20)
print(str(d.total_seconds())
# 350120.0

@tomchristie
Copy link
Member

Thanks - good to talk this through from a docs-first perspective.

@stale
Copy link

stale bot commented Oct 16, 2022

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Oct 16, 2022
@stale stale bot closed this Nov 1, 2022
@pietzschke
Copy link

Can we reopen this?

@auvipy auvipy reopened this Jun 4, 2024
@stale stale bot removed the stale label Jun 4, 2024
@pietzschke
Copy link

I think it's not possible to contribute to the pr to solve the conflicts, @sevdog would you be able to do it?
Otherwise i will create a new one and do the fixes.

@sevdog sevdog force-pushed the duration-format branch from 9d506ba to 9713c1d Compare June 5, 2024 09:28
@sevdog
Copy link
Contributor Author

sevdog commented Jun 5, 2024

Just rebased the PR, the "conflict" was a very silly issue in the doc file 🤷‍♂️

CI is failing due to a change in django-main branch, it should be addressed in an other PR.

@sevdog sevdog marked this pull request as ready for review June 5, 2024 09:33
@sevdog sevdog force-pushed the duration-format branch 2 times, most recently from 295a462 to 70751a5 Compare July 8, 2024 12:41
Copy link
Member

@auvipy auvipy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw FAILED tests/test_fields.py::TestISOOutputFormatDurationField::test_outputs
=========== 1 failed,

@sevdog sevdog force-pushed the duration-format branch from 70751a5 to 35398b3 Compare July 9, 2024 16:36
@sevdog
Copy link
Contributor Author

sevdog commented Jul 9, 2024

Ok, fixed the test. It was a long lasting double typo (wrong class and wrong format).

@auvipy auvipy requested a review from a team July 10, 2024 09:24
peterthomassen
peterthomassen previously approved these changes Jul 10, 2024
@sevdog sevdog force-pushed the duration-format branch from 35398b3 to bcc4ebf Compare July 11, 2024 06:13
@auvipy
Copy link
Member

auvipy commented Jul 11, 2024

we should merge this only in a major release

Copy link

stale bot commented Apr 26, 2025

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Apr 26, 2025
@ulgens
Copy link
Contributor

ulgens commented Apr 27, 2025

The fix seems still relevant.

@auvipy auvipy removed the stale label Apr 27, 2025
Copy link
Member

@auvipy auvipy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this will be accepted or not, but if it is accepted, fixing the merge conflict should be enough

@sevdog
Copy link
Contributor Author

sevdog commented Apr 28, 2025

Conflict revolved.

Copy link
Contributor

@peterthomassen peterthomassen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for resolving the conflict; some more comments here!

Comment on lines +383 to +388
* `format` - A string representing the output format. If not specified, this defaults to the same value as the `DURATION_FORMAT` settings key, which will be `'standard'` unless set. Setting to a format string indicates that `to_representation` return values should be coerced to string output. Format strings are described below. Setting this value to `None` indicates that Python `timedelta` objects should be returned by `to_representation`. In this case the date encoding will be determined by the renderer.
* `max_value` Validate that the duration provided is no greater than this value.
* `min_value` Validate that the duration provided is no less than this value.

#### `DurationField` format strings
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'`).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the use of the term "format string" very confusing here. The term means something else in Python, referring to string with placeholder variables that get filled in. That sounds like one could set format="%h:%m:%s" to get the duration in hours, minutes, seconds, but that is not the case. Please find another word, like "format family", "format variety", ... or something.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, how about format_name?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used the "format" name just to keep uniformity with date/time fields, because to me it looked more elegant.

Since there is nothing like strftime/strptime for timedelta this may also just be a flag if it is clear/readable enough.

@@ -21,6 +21,7 @@

# Default datetime input and output formats
ISO_8601 = 'iso-8601'
STD_DURATION = 'standard'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It turns out that this value isn't used anywhere; as long as format is set to anything else but ISO_8601, we end up with 'standard' behavior.

That's probably not intended. Can this be correct? Let's also add a test case for the behavior with an unknown format value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That value was placed just to be a placeholder for the default format. It may just be set to None in settings.py. I added the "standard" after this conversation just to improve readability. The format-as-ISO may also be a flag, however that pattern may be harder to read and would add a non-uniformity with other time-related fields.

It is true that django itself does not provide much flexibility with duration representation, which also comes from stdlib timedelta which has no implementation with to-str or from-str like date/time does.

Which way do you prefer for completing this?

@@ -1351,9 +1351,11 @@ class DurationField(Field):
'overflow': _('The number of days must be between {min_days} and {max_days}.'),
}

def __init__(self, **kwargs):
def __init__(self, format=empty, **kwargs):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should disallow specifying this as positional argument: DurationField('iso-8601') (we don't want that).

All other options come from the **kwargs...

Comment on lines +1792 to +1793
valid_inputs = {}
invalid_inputs = {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to add test coverage for parsing duration in ISO format, as I would expect it to work both ways

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently the parse_duration method of django, which is used inside DurationField, already parses ISO-8601 format and the format specific to postgres by its own.

This could be added to the TestDurationField testcase.

Where do you prefer these tests to be added?

PS:
From this point of view the current error message "Use one of these formats instead: [DD] [HH:[MM:]]ss[.uuuuuu]" is not (completely) right, however changing it would be a breaking change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure where that an extension of this error message should be considered a breaking change; the wording "one of" already hints that the application should not rely the one specific format being given in the message.

Comment on lines +383 to +388
* `format` - A string representing the output format. If not specified, this defaults to the same value as the `DURATION_FORMAT` settings key, which will be `'standard'` unless set. Setting to a format string indicates that `to_representation` return values should be coerced to string output. Format strings are described below. Setting this value to `None` indicates that Python `timedelta` objects should be returned by `to_representation`. In this case the date encoding will be determined by the renderer.
* `max_value` Validate that the duration provided is no greater than this value.
* `min_value` Validate that the duration provided is no less than this value.

#### `DurationField` format strings
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'`).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, how about format_name?

auvipy and others added 2 commits April 29, 2025 12:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants