fix: strptime swaps day/year for DD-MM-YYYY dates#120
Conversation
When parsing European-style dates like "31-12-2023", the swap logic
on line 143 assumed the first numeric field was always a year (for
YYYY-MM-DD mainframe format). This caused day and year values to be
exchanged: strptime("31-12-2023") returned day=2023, year=31.
Fix: when the initial month value exceeds 12, check whether the first
field could be a day (<=31) or must be a year (>31) to distinguish
DD-MM-YYYY from YYYY-MM-DD formats.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Greptile SummaryThis PR fixes
Confidence Score: 4/5Not safe to merge as-is — the off-by-one in the boundary condition leaves day=13 dates still broken after the fix One P1 defect remains: lib/Date/Parse.pm line 143 — boundary condition off-by-one
|
| Filename | Overview |
|---|---|
| lib/Date/Parse.pm | Fixes DD-MM-YYYY day/year swap but off-by-one in boundary condition (> 12 instead of >= 12) leaves day=13 dates still broken |
| t/ddmmyyyy.t | New test file covering DD-MM-YYYY, DD-MM-YY, and YYYY-MM-DD formats; misses a day=13 boundary case that would expose the remaining off-by-one bug |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["Regex match: (\\d+)-(\\d\\d?)-(\\d+)\n$1=first, $3=middle, $5=last"] --> B["Initial assignment:\nmonth = $1 - 1\nday = $3\nyear = $5"]
B --> C{"month > 12?\ni.e. $1 > 13"}
C -- No --> G["Keep: month=$1-1, day=$3, year=$5\n⚠️ day=13 falls here incorrectly"]
C -- Yes --> D{"$1 > 31?"}
D -- Yes --> E["YYYY-MM-DD\nyear=$1, month=$3-1, day=$5"]
D -- No --> F["DD-MM-YYYY\nyear=$5, month=$3-1, day=$1"]
E --> H["return if year > 2 digits and year < 1901"]
F --> H
G --> H
style G fill:#ffcccc,stroke:#cc0000
Reviews (1): Last reviewed commit: "fix: strptime swaps day/year for DD-MM-Y..." | Re-trigger Greptile
| # Possible match for 1995-01-24 (short mainframe date format); | ||
| ($year,$month,$day) = ($1, $3 - 1, $5) if $month > 12; | ||
| # Possible match for 1995-01-24 or 31-12-2023 | ||
| if ($month > 12) { |
There was a problem hiding this comment.
Off-by-one: day=13 in DD-MM-YYYY still broken
The condition $month > 12 translates to ($1 - 1) > 12, i.e. $1 > 13. That means a date like "13-06-2023" produces $month = 12 (invalid 0-based month) and never enters the fix branch — 12 > 12 is false. The result would be month=12, day=6, year=2023 (month 12 is out of range), so str2time("13-06-2023") still returns undef. The check should be $month >= 12 (i.e. $1 > 12) to cover any first-field value that cannot be a valid month.
| if ($month > 12) { | |
| if ($month >= 12) { |
| # DD-MM-YYYY (European date format) — the first field exceeds 12, | ||
| # so it cannot be a month. Before the fix, day and year were swapped. |
There was a problem hiding this comment.
Comment overstates fix coverage; boundary case untested
The comment says "the first field exceeds 12" but every test case uses a day ≥ 25. The exact boundary the current code misses — day=13, where $month = 12 and 12 > 12 is false — has no test. Adding a case like strptime("13-06-2023") would catch the off-by-one in the > 12 vs >= 12 condition and match the comment's stated invariant.
What
Fix
strptimereturning swapped day/year values when parsing European-style dates (DD-MM-YYYY).Why
strptime("31-12-2023")returned day=2023, month=11, year=31 instead of day=31, month=11, year=123. This madestr2time("31-12-2023")returnundefsince day=2023 is invalid. Any date where the first field exceeds 12 and the last field is the year (e.g., "25-07-2020") was broken.How
The existing swap logic on line 143 assumed
$1(first numeric field) was always a year when$month > 12. This is only true for YYYY-MM-DD (mainframe format where$1 > 31). For DD-MM-YYYY format ($1 <= 31), the year is in$5and the day in$1. Added a check to distinguish the two cases.Testing
t/ddmmyyyy.twith 12 tests covering DD-MM-YYYY, DD-MM-YY, and YYYY-MM-DD formats🤖 Generated with Claude Code
Quality Report
Changes: 2 files changed, 46 insertions(+), 2 deletions(-)
Code scan: clean
Tests: skipped
Branch hygiene: clean
Generated by Kōan post-mission quality pipeline