Skip to content

fix: strptime swaps day/year for DD-MM-YYYY dates#120

Merged
atoomic merged 1 commit into
cpan-authors:mainfrom
Koan-Bot:koan.atoomic/fix-ddmmyyyy-swap
Apr 26, 2026
Merged

fix: strptime swaps day/year for DD-MM-YYYY dates#120
atoomic merged 1 commit into
cpan-authors:mainfrom
Koan-Bot:koan.atoomic/fix-ddmmyyyy-swap

Conversation

@Koan-Bot
Copy link
Copy Markdown

@Koan-Bot Koan-Bot commented Apr 20, 2026

What

Fix strptime returning 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 made str2time("31-12-2023") return undef since 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 $5 and the day in $1. Added a check to distinguish the two cases.

Testing

  • New t/ddmmyyyy.t with 12 tests covering DD-MM-YYYY, DD-MM-YY, and YYYY-MM-DD formats
  • Full test suite passes (592 tests)

🤖 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

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>
@atoomic atoomic marked this pull request as ready for review April 26, 2026 12:28
@atoomic atoomic merged commit c21190c into cpan-authors:main Apr 26, 2026
22 checks passed
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 26, 2026

Greptile Summary

This PR fixes strptime swapping day and year for European-style DD-MM-YYYY dates by distinguishing YYYY-MM-DD (first field > 31) from DD-MM-YYYY (first field ≤ 31) when the initial month parse produces an out-of-range value.

  • P1 — off-by-one in boundary condition: The guard on line 143 is $month > 12, which equals $1 > 13. Dates where the day field is exactly 13 (e.g. \"13-06-2023\") yield $month = 12, and 12 > 12 is false, so no swap occurs. The result is an invalid month=12 (0-based), and str2time still returns undef. The condition should be $month >= 12.

Confidence Score: 4/5

Not 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: $month > 12 should be $month >= 12. Dates with day=13 (e.g. "13-06-2023") still produce an invalid month and return undef from str2time. The fix is a one-character change and the tests cover the rest of the logic correctly.

lib/Date/Parse.pm line 143 — boundary condition off-by-one

Important Files Changed

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
Loading

Reviews (1): Last reviewed commit: "fix: strptime swaps day/year for DD-MM-Y..." | Re-trigger Greptile

Comment thread lib/Date/Parse.pm
# 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) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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.

Suggested change
if ($month > 12) {
if ($month >= 12) {

Comment thread t/ddmmyyyy.t
Comment on lines +6 to +7
# 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.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

@Koan-Bot Koan-Bot deleted the koan.atoomic/fix-ddmmyyyy-swap branch April 27, 2026 09:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants