[Django] #36747: parse_duration() fails to parse valid ISO-8601 durations including years, months, and weeks due to incorrect regex

11 views
Skip to first unread message

Django

unread,
Nov 20, 2025, 5:35:23 AMNov 20
to django-...@googlegroups.com
#36747: parse_duration() fails to parse valid ISO-8601 durations including years,
months, and weeks due to incorrect regex
--------------------------------+-----------------------------------------
Reporter: florianvazelle | Type: Bug
Status: new | Component: Uncategorized
Version: 5.2 | Severity: Normal
Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
--------------------------------+-----------------------------------------
== **Description**

`django.utils.dateparse.parse_duration()` claims to support ISO-8601
duration strings, but the current implementation only handles the
`PnDTnHnMnS` subset. Valid ISO-8601 components such as years (`Y`), months
(`M`), and weeks (`W`) are not supported.

The internal regex used for ISO-8601 durations:

{{{
iso8601_duration_re = _lazy_re_compile(
r"^(?P<sign>[-+]?)"
r"P"
r"(?:(?P<days>\d+([.,]\d+)?)D)?"
r"(?:T"
r"(?:(?P<hours>\d+([.,]\d+)?)H)?"
r"(?:(?P<minutes>\d+([.,]\d+)?)M)?"
r"(?:(?P<seconds>\d+([.,]\d+)?)S)?"
r")?"
r"$"
)
}}}

This means Django currently rejects valid duration strings such as:


{{{
P1Y2M
P3W
P1Y
P2M10DT2H
}}}



Despite the documentation suggesting ISO-8601 support, these forms cannot
be parsed.

== **Steps to Reproduce**

{{{
from django.utils.dateparse import parse_duration

parse_duration("P1Y") # returns None
parse_duration("P2M") # returns None
parse_duration("P3W") # returns None
parse_duration("P1Y2M3DT4H") # returns None
}}}

== **Expected Behavior**

`parse_duration()` should parse all valid ISO-8601 durations that can be
represented as `timedelta`, including:

* `PnW`
* `PnYnMnD`
* ...

Django should capture each calendar unit, or clearly state limitations.

== **Proposed Fix**

1. Replace the current `iso8601_duration_re` with one that uses distinct
group names for each ISO-8601 calendar unit:



{{{
iso8601_duration_re = _lazy_re_compile(
r"^(?P<sign>[-+]?)"
r"P"
r"(?:(?P<years>\d+([.,]\d+)?)Y)?"
r"(?:(?P<months>\d+([.,]\d+)?)M)?"
r"(?:(?P<weeks>\d+([.,]\d+)?)W)?"
r"(?:(?P<days>\d+([.,]\d+)?)D)?"
r"(?:T"
r"(?:(?P<hours>\d+([.,]\d+)?)H)?"
r"(?:(?P<minutes>\d+([.,]\d+)?)M)?"
r"(?:(?P<seconds>\d+([.,]\d+)?)S)?"
r")?"
r"$"
)
}}}



2. Extend `parse_duration()` to convert these new fields to timedelta.


{{{
def parse_duration(value):
match = (
standard_duration_re.match(value)
or iso8601_duration_re.match(value)
or postgres_interval_re.match(value)
)
if match:
kw = match.groupdict()
sign = -1 if kw.pop("sign", "+") == "-" else 1
if kw.get("microseconds"):
kw["microseconds"] = kw["microseconds"].ljust(6, "0")
kw = {k: float(v.replace(",", ".")) for k, v in kw.items() if v is
not None}
days = datetime.timedelta(kw.pop("days", 0.0) or 0.0)

if match.re == iso8601_duration_re:
+ years = kw.pop("years", 0.0)
+ months = kw.pop("months", 0.0)
+ weeks = kw.pop("weeks", 0.0)
+
+ days = datetime.timedelta(years=years, months=months,
days=kw.pop("days", 0.0) + (weeks * 7))
days *= sign

return days + sign * datetime.timedelta(**kw)
}}}


I can provide a full patch (tests + implementation) if desired.
--
Ticket URL: <https://code.djangoproject.com/ticket/36747>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
Nov 20, 2025, 5:47:50 AMNov 20
to django-...@googlegroups.com
#36747: parse_duration() fails to parse valid ISO-8601 durations including years,
months, and weeks due to incorrect regex
--------------------------------+--------------------------------------
Reporter: florianvazelle | Owner: (none)
Type: Bug | Status: new
Component: Uncategorized | Version: 5.2
Severity: Normal | Resolution:
Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
--------------------------------+--------------------------------------
Description changed by florianvazelle:

Old description:
New description:
--
--
Ticket URL: <https://code.djangoproject.com/ticket/36747#comment:1>

Django

unread,
Nov 20, 2025, 8:33:34 AMNov 20
to django-...@googlegroups.com
#36747: parse_duration() fails to parse valid ISO-8601 durations including years,
months, and weeks due to incorrect regex
--------------------------------+--------------------------------------
Reporter: florianvazelle | Owner: (none)
Type: Bug | Status: new
Component: Uncategorized | Version: 5.2
Severity: Normal | Resolution:
Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
--------------------------------+--------------------------------------
Comment (by David):

This is a limit of supporting Python's `timedelta` which does not support
years and months: https://docs.python.org/3/library/datetime.html
#timedelta-objects.
Yet it supports weeks, thus the parsing in django could be improved to
also support ISO8601 duration with weeks.

If you need to support every IS08601 duration you can use
https://github.com/CodeYellowBV/django-relativedelta which offers support
for `relativedelta` which implements a more complete timedelta class.
--
Ticket URL: <https://code.djangoproject.com/ticket/36747#comment:2>

Django

unread,
Nov 21, 2025, 12:10:17 PMNov 21
to django-...@googlegroups.com
#36747: parse_duration() fails to parse valid ISO-8601 durations including years,
months, and weeks due to incorrect regex
--------------------------------------+------------------------------------
Reporter: florianvazelle | Owner: (none)
Type: Cleanup/optimization | Status: new
Component: Utilities | Version: dev
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
--------------------------------------+------------------------------------
Changes (by Jacob Walls):

* component: Uncategorized => Utilities
* stage: Unreviewed => Accepted
* type: Bug => Cleanup/optimization
* version: 5.2 => dev

Comment:

Very helpful triage, David. I'd accept a PR that added support for weeks
and documented that years and months are not supported.
--
Ticket URL: <https://code.djangoproject.com/ticket/36747#comment:3>

Django

unread,
Nov 22, 2025, 12:22:29 AMNov 22
to django-...@googlegroups.com
#36747: parse_duration() fails to parse valid ISO-8601 durations including years,
months, and weeks due to incorrect regex
-------------------------------------+-------------------------------------
Reporter: florianvazelle | Owner: Varun
Type: | Kasyap Pentamaraju
Cleanup/optimization | Status: assigned
Component: Utilities | Version: dev
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Varun Kasyap Pentamaraju):

* owner: (none) => Varun Kasyap Pentamaraju
* status: new => assigned

--
Ticket URL: <https://code.djangoproject.com/ticket/36747#comment:4>

Django

unread,
Nov 22, 2025, 7:40:13 AMNov 22
to django-...@googlegroups.com
#36747: parse_duration() fails to parse valid ISO-8601 durations including years,
months, and weeks due to incorrect regex
-------------------------------------+-------------------------------------
Reporter: florianvazelle | Owner: Varun
Type: | Kasyap Pentamaraju
Cleanup/optimization | Status: assigned
Component: Utilities | Version: dev
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Varun Kasyap Pentamaraju):

* has_patch: 0 => 1

--
Ticket URL: <https://code.djangoproject.com/ticket/36747#comment:5>

Django

unread,
1:42 PM (4 hours ago) 1:42 PM
to django-...@googlegroups.com
#36747: parse_duration() fails to parse valid ISO-8601 durations including years,
months, and weeks due to incorrect regex
-------------------------------------+-------------------------------------
Reporter: florianvazelle | Owner: Varun
| Kasyap Pentamaraju
Type: New feature | Status: assigned
Component: Utilities | Version: dev
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 1
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Jacob Walls):

* needs_docs: 0 => 1
* type: Cleanup/optimization => New feature

--
Ticket URL: <https://code.djangoproject.com/ticket/36747#comment:6>
Reply all
Reply to author
Forward
0 new messages