| 55 | | |
| 56 | | == **Proposed Fix** |
| 57 | | |
| 58 | | 1. Replace the current `iso8601_duration_re` with one that uses distinct group names for each ISO-8601 calendar unit: |
| 59 | | |
| 60 | | |
| 61 | | |
| 62 | | {{{ |
| 63 | | iso8601_duration_re = _lazy_re_compile( |
| 64 | | r"^(?P<sign>[-+]?)" |
| 65 | | r"P" |
| 66 | | r"(?:(?P<years>\d+([.,]\d+)?)Y)?" |
| 67 | | r"(?:(?P<months>\d+([.,]\d+)?)M)?" |
| 68 | | r"(?:(?P<weeks>\d+([.,]\d+)?)W)?" |
| 69 | | r"(?:(?P<days>\d+([.,]\d+)?)D)?" |
| 70 | | r"(?:T" |
| 71 | | r"(?:(?P<hours>\d+([.,]\d+)?)H)?" |
| 72 | | r"(?:(?P<minutes>\d+([.,]\d+)?)M)?" |
| 73 | | r"(?:(?P<seconds>\d+([.,]\d+)?)S)?" |
| 74 | | r")?" |
| 75 | | r"$" |
| 76 | | ) |
| 77 | | }}} |
| 78 | | |
| 79 | | |
| 80 | | |
| 81 | | 2. Extend `parse_duration()` to convert these new fields to timedelta. |
| 82 | | |
| 83 | | |
| 84 | | {{{ |
| 85 | | def parse_duration(value): |
| 86 | | match = ( |
| 87 | | standard_duration_re.match(value) |
| 88 | | or iso8601_duration_re.match(value) |
| 89 | | or postgres_interval_re.match(value) |
| 90 | | ) |
| 91 | | if match: |
| 92 | | kw = match.groupdict() |
| 93 | | sign = -1 if kw.pop("sign", "+") == "-" else 1 |
| 94 | | if kw.get("microseconds"): |
| 95 | | kw["microseconds"] = kw["microseconds"].ljust(6, "0") |
| 96 | | kw = {k: float(v.replace(",", ".")) for k, v in kw.items() if v is not None} |
| 97 | | days = datetime.timedelta(kw.pop("days", 0.0) or 0.0) |
| 98 | | |
| 99 | | if match.re == iso8601_duration_re: |
| 100 | | + years = kw.pop("years", 0.0) |
| 101 | | + months = kw.pop("months", 0.0) |
| 102 | | + weeks = kw.pop("weeks", 0.0) |
| 103 | | + |
| 104 | | + days = datetime.timedelta(years=years, months=months, days=kw.pop("days", 0.0) + (weeks * 7)) |
| 105 | | days *= sign |
| 106 | | |
| 107 | | return days + sign * datetime.timedelta(**kw) |
| 108 | | }}} |
| 109 | | |
| 110 | | |
| 111 | | I can provide a full patch (tests + implementation) if desired. |