Reject negative numbers in dehumanize instead of dropping the sign - #1333
Reject negative numbers in dehumanize instead of dropping the sign#1333dchaudhari7177 wants to merge 2 commits into
Conversation
`dehumanize("in -1 hours")` returned the same time as
`dehumanize("in 1 hours")`. The number pattern is `\d+`, so the minus
sign was never captured and the result silently pointed the opposite way.
A humanized string carries its direction in the "ago"/"in" wording, so a
sign on the number is never meaningful; raise ValueError rather than
guess. None of the 133 dehumanize locales has a hyphen anywhere in its
past, future or timeframe strings, so a hyphen in front of a digit can
only have come from the caller.
Fixes arrow-py#1278
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1333 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 10 10
Lines 2315 2317 +2
Branches 358 359 +1
=========================================
+ Hits 2315 2317 +2 ☔ View full report in Codecov by Harness. |
There was a problem hiding this comment.
Pull request overview
This PR updates Arrow.dehumanize() to explicitly reject negative numeric tokens (e.g., "in -1 hours") instead of silently ignoring the - sign and producing an incorrect time shift, and adds regression tests to cover the new behavior.
Changes:
- Add an early validation in
Arrow.dehumanize()that raisesValueErrorwhen the input contains a hyphen directly before a digit. - Add tests ensuring negative-number inputs raise, while equivalent positive inputs still parse correctly.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
arrow/arrow.py |
Adds early input validation to reject negative-number patterns in dehumanize() to avoid silently incorrect results. |
tests/test_arrow.py |
Adds regression tests for negative-number rejection and ensures positive equivalents still work. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| if re.search(r"-\d", input_string): | ||
| raise ValueError( |
The -\d guard also matched an ISO date: dehumanize("2020-01-01") raised
"String contains a negative number", which sends the caller looking for a sign
that is not there. A hyphen between digits is a separator.
A negative lookbehind leaves those to the existing "Input string not valid"
error, and the sign positions the guard exists for — start of string, after a
space, after a bracket — still raise.
|
Good catch, and fixed. The guard was A hyphen between digits is a separator, so the guard now uses a negative lookbehind, Two tests added — one pinning that the ISO dates get the ordinary error, one pinning that Full suite: 1903 passed, 1 skipped. Separately, on the red A truncated PyPI download while building pyyaml from source. #1334 failed the same way on the same runner. The push above should re-run it. |
Fixes #1278.
Problem
The number pattern in
dehumanizeisre.compile(r"\d+"), so the minus sign is never part of the match. The sign is dropped without a word and the caller gets a time shifted the opposite way —"in -2 days"becomes +2 days,"-3 minutes ago"becomes -3 minutes. Wrong answers of this shape are hard to trace back to their cause.Fix
Raise
ValueErrorwhen the input contains a hyphen directly in front of a digit. A humanized string expresses direction with words (ago/in ...), so a sign on the number never carries meaning — there is no correct value to fall back to, and guessing one is what caused the bug.The check is narrow enough to be safe: I enumerated all 133 locales in
DEHUMANIZE_LOCALESand none has a hyphen anywhere in itspast,future, ortimeframesstrings, so-\din the input can only have come from the caller. It also cannot fire on a hyphen used as a word separator, since it requires a digit immediately after.The error message names the problem and shows the intended form.
Tests
test_negative_number— four negative inputs across units and both directions, each raisingValueErrormatching"negative number".test_negative_number_not_confused_with_positive— the unsigned equivalents still resolve to the right shift in both directions.Full suite passes (1901 passed, 1 skipped).
🤖 Generated with Claude Code