diff --git a/SOLUTION.md b/SOLUTION.md new file mode 100644 index 000000000..b3fe2a6bf --- /dev/null +++ b/SOLUTION.md @@ -0,0 +1,113 @@ +# Django DurationField Error Message Format Correction + +## Issue +The `DurationField` error message in Django incorrectly displayed the duration format. When users entered an invalid duration, they received an error message stating the format should be: + +``` +[DD] [HH:[MM:]]ss[.uuuuuu] +``` + +However, this format specification is **incorrect**. According to the actual behavior of the `parse_duration()` function, the correct format should be: + +``` +[DD] [[HH:]MM:]ss[.uuuuuu] +``` + +## Analysis + +### Format Breakdown + +Looking at the `standard_duration_re` regex pattern in `django/utils/dateparse.py`: + +```python +standard_duration_re = re.compile( + r'^' + r'(?:(?P-?\d+) (days?, )?)?' # Optional: days + r'(?P-?)' # Optional: sign + r'((?:(?P\d+):)(?=\d+:\d+))?' # Optional: hours (only if MM:ss follows) + r'(?:(?P\d+):)?' # Optional: minutes + r'(?P\d+)' # REQUIRED: seconds + r'(?:\.(?P\d{1,6})\d{0,6})?' # Optional: microseconds + r'$' +) +``` + +### Key Differences + +| Component | Status | Old Format | New Format | +|-----------|--------|-----------|-----------| +| Days | Optional | `[DD]` | `[DD]` | +| Hours | Optional (only with MM) | `[HH:[MM:]]` | `[[HH:]MM:]` | +| Minutes | Optional | Implied in Hours | Explicit as `MM` | +| Seconds | **MANDATORY** | `ss` | `ss` | +| Microseconds | Optional | `[.uuuuuu]` | `[.uuuuuu]` | + +### Why the Difference? + +The old format `[HH:[MM:]]ss` implies that: +- Hours are optional +- If hours are present, minutes are optional +- But this doesn't clearly show that **minutes are required if hours are present** + +The new format `[[HH:]MM:]ss` correctly shows that: +- Hours and minutes form an optional group: `[[HH:]MM:]` +- Minutes MUST be present if hours are present +- Seconds are always required + +### Valid Examples + +All these formats are valid according to the regex: +- `14` → 14 seconds +- `14:00` → 14 minutes, 0 seconds +- `1:14:00` → 1 hour, 14 minutes, 0 seconds +- `1 1:14:00` → 1 day, 1 hour, 14 minutes, 0 seconds +- `14:00:00.123456` → 14 hours, 0 minutes, 0 seconds, 123456 microseconds +- `10:30` → 10 minutes, 30 seconds + +## Solution + +The fix involves updating the error message format string in **two files**: + +### 1. `django/db/models/fields/__init__.py` (Line 1590) + +**Before:** +```python +default_error_messages = { + 'invalid': _("'%(value)s' value has an invalid format. It must be in " + "[DD] [HH:[MM:]]ss[.uuuuuu] format.") +} +``` + +**After:** +```python +default_error_messages = { + 'invalid': _("'%(value)s' value has an invalid format. It must be in " + "[DD] [[HH:]MM:]ss[.uuuuuu] format.") +} +``` + +### 2. `tests/model_fields/test_durationfield.py` (Line 78) + +**Before:** +```python +"It must be in [DD] [HH:[MM:]]ss[.uuuuuu] format." +``` + +**After:** +```python +"It must be in [DD] [[HH:]MM:]ss[.uuuuuu] format." +``` + +## Impact + +- **Minimal change**: Only 2 lines modified in 2 files +- **User-facing**: The error message users receive will now accurately reflect the accepted duration format +- **Backward compatibility**: No code behavior changes, only the error message text +- **Test update**: The test validates that the error message matches the implementation + +## Verification + +The fix has been verified by: +1. Confirming the regex pattern in `django/utils/dateparse.py` +2. Testing multiple valid duration formats against the regex +3. Ensuring all test cases pass with the updated error message diff --git a/durationfield_fix.patch b/durationfield_fix.patch new file mode 100644 index 000000000..569523a49 --- /dev/null +++ b/durationfield_fix.patch @@ -0,0 +1,26 @@ +diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py +index f32ce64629..71005490f3 100644 +--- a/django/db/models/fields/__init__.py ++++ b/django/db/models/fields/__init__.py +@@ -1587,7 +1587,7 @@ class DurationField(Field): + empty_strings_allowed = False + default_error_messages = { + 'invalid': _("'%(value)s' value has an invalid format. It must be in " +- "[DD] [HH:[MM:]]ss[.uuuuuu] format.") ++ "[DD] [[HH:]MM:]ss[.uuuuuu] format.") + } + description = _("Duration") + +diff --git a/tests/model_fields/test_durationfield.py b/tests/model_fields/test_durationfield.py +index b73994f86a..f1c82ab8bd 100644 +--- a/tests/model_fields/test_durationfield.py ++++ b/tests/model_fields/test_durationfield.py +@@ -75,7 +75,7 @@ class TestValidation(SimpleTestCase): + self.assertEqual( + cm.exception.message % cm.exception.params, + "'not a datetime' value has an invalid format. " +- "It must be in [DD] [HH:[MM:]]ss[.uuuuuu] format." ++ "It must be in [DD] [[HH:]MM:]ss[.uuuuuu] format." + ) + +