Skip to content

fix: align Date::Language::str2time with Date::Parse behavior#110

Merged
atoomic merged 2 commits intocpan-authors:mainfrom
Koan-Bot:koan.atoomic/fix-language-str2time
Apr 29, 2026
Merged

fix: align Date::Language::str2time with Date::Parse behavior#110
atoomic merged 2 commits intocpan-authors:mainfrom
Koan-Bot:koan.atoomic/fix-language-str2time

Conversation

@Koan-Bot
Copy link
Copy Markdown

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

What

Fixes two behavioral differences between Date::Language::str2time and Date::Parse::str2time.

Why

Date::Language::str2time and Date::Parse::str2time should behave consistently for the same inputs. Two discrepancies caused surprising results:

  1. Year inference bug: parsing "15 Apr" on April 12 gave 2026 (Date::Language) vs 2025 (Date::Parse). Date::Language only compared months, missing the day-of-month check for same-month future dates.

  2. Fatal error on invalid input: Date::Language::str2time("32 Jun 2024") died with Day '32' out of range, while Date::Parse::str2time returns undef. The timegm/timelocal calls were unprotected.

How

  • Added day comparison to the year-inference heuristic (matching Date::Parse's logic)
  • Added input validation (month/day/hour/minute/second range checks)
  • Wrapped timegm/timelocal in eval blocks to return undef on failure

Testing

New t/lang-str2time.t covers:

  • Same-month future/past day year inference (parity with Date::Parse)
  • Invalid dates return undef instead of dying (day=32, hour=25, minute=61)
  • Basic valid date parsing and non-English round-trip

Full test suite passes (28 test files).

🤖 Generated with Claude Code


Quality Report

Changes: 2 files changed, 137 insertions(+), 4 deletions(-)

Code scan: clean

Tests: skipped

Branch hygiene: clean

Generated by Kōan post-mission quality pipeline

@atoomic atoomic marked this pull request as ready for review April 26, 2026 13:02
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 26, 2026

Greptile Summary

This PR fixes two real behavioral divergences between Date::Language::str2time and Date::Parse::str2time: the missing day-of-month comparison in year inference, and unprotected timegm/timelocal calls that die on invalid input. The fixes are correct, but the eval blocks stop short of full parity with Date::Parse.

  • The timegm/timelocal eval paths are missing the -1 sentinel check and the $result < 0 && $year >= 1970 overflow guard that Date::Parse::str2time uses; on older Time::Local installations these guards are load-bearing, and their absence means the two functions still diverge for edge-case inputs.

Confidence Score: 4/5

Safe to merge after addressing the missing -1 / overflow guards in the eval blocks.

One P1 finding: the eval blocks don't replicate Date::Parse's -1 sentinel and negative-result overflow checks, leaving a behavioral gap on older Time::Local versions and 32-bit platforms. All other findings are P2.

lib/Date/Language.pm — the timegm/timelocal eval paths (lines 107–121)

Important Files Changed

Filename Overview
lib/Date/Language.pm Year-inference and eval-wrapping fixes are correct, but the new eval blocks are missing the -1 sentinel and negative-result overflow guards present in the Date::Parse reference implementation.
t/lang-str2time.t New test file covering year inference, invalid input handling, and a German round-trip; has an unused POSIX qw(mktime) import and skips the future-day test on days 28–31.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[str2time input] --> B[strptime parse]
    B --> C{result empty?}
    C -- yes --> D[return undef]
    C -- no --> E[fill defaults: hh/mm/ss/month/day]
    E --> F{year defined?}
    F -- no --> G{month > cur_month OR same month and day > cur_day?}
    G -- yes --> H[year = last year]
    G -- no --> I[year = current year]
    F -- yes --> J[use parsed year]
    H --> K
    I --> K
    J --> K[Validate ranges: month<=11, day 1-31, hh<=23, mm<=59, ss<=59]
    K -- invalid --> D
    K -- valid --> L{zone defined?}
    L -- yes --> M[eval timegm / catch exception]
    L -- no --> N[eval timelocal / catch exception]
    M --> O{result defined?}
    N --> O
    O -- no --> D
    O -- yes --> P[result -= zone if zoned]
    P --> Q[return result]
Loading

Reviews (1): Last reviewed commit: "fix: align Date::Language::str2time with..." | Re-trigger Greptile

Comment thread lib/Date/Language.pm
Comment on lines +107 to +121
if (defined $zone) {
$result = eval {
local $SIG{__DIE__} = sub {}; # match Date::Parse behavior
timegm($ss,$mm,$hh,$day,$month,$year);
};
return undef unless defined $result;
$result -= $zone;
}
else {
$result = eval {
local $SIG{__DIE__} = sub {}; # match Date::Parse behavior
timelocal($ss,$mm,$hh,$day,$month,$year);
};
return undef unless defined $result;
}
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 Missing -1 sentinel and overflow guards from Date::Parse

The PR claims parity with Date::Parse::str2time, but the eval blocks are missing two checks that exist in the reference implementation.

  1. On older Time::Local versions (pre-1.23, ~pre-2011), timegm/timelocal returns -1 to signal error instead of dying. The eval won't catch that, so -1 would be returned as a valid epoch to callers.
  2. Integer overflow on 32-bit systems can cause timegm/timelocal to return a large negative value; Date::Parse rejects $result < 0 && $year >= 1970.

The corresponding guard in Date::Parse::str2time (lines 302–308 / 317–323):

return undef
  if !defined $result
     or $result == -1
        && join("",$ss,$mm,$hh,$day,$month,$year)
             ne "595923311169";
return undef if $result < 0 && $year >= 1970;

Without these, the two code paths diverge for dates on or near epoch −1, and for overflow inputs on 32-bit platforms.

Comment thread t/lang-str2time.t Outdated
use Test::More;
use Date::Language;
use Date::Parse;
use POSIX qw(mktime);
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 Unused import mktime

POSIX qw(mktime) is imported but mktime is never called anywhere in the test file. This can be removed.

Suggested change
use POSIX qw(mktime);
use POSIX;

or simply drop the use POSIX line entirely if no other POSIX symbols are needed.

Comment thread lib/Date/Language.pm
Comment on lines +101 to +103
return undef
unless(defined $month && $month <= 11 && $day >= 1 && $day <= 31
&& $hh <= 23 && $mm <= 59 && $ss <= 59);
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 Redundant defined $month guard

$month is unconditionally assigned from $lt[4] on lines 89–90 when it was previously undefined, so by line 101 it is always defined. The defined $month check is dead code. Date::Parse::str2time omits it for the same reason.

Suggested change
return undef
unless(defined $month && $month <= 11 && $day >= 1 && $day <= 31
&& $hh <= 23 && $mm <= 59 && $ss <= 59);
return undef
unless($month <= 11 && $day >= 1 && $day <= 31
&& $hh <= 23 && $mm <= 59 && $ss <= 59);

@atoomic
Copy link
Copy Markdown
Collaborator

atoomic commented Apr 27, 2026

@Koan-Bot rebase

Koan-Bot and others added 2 commits April 26, 2026 23:56
Two bugs in Date::Language::str2time compared to Date::Parse::str2time:

1. Year inference missed day comparison: when parsing a date without a
   year where the month matches the current month, only the month was
   compared. This meant "15 Apr" parsed on April 12 would resolve to
   the current year (future date) instead of the previous year, unlike
   Date::Parse which correctly treats same-month future days as
   referring to last year.

2. No error protection: timegm/timelocal could die on invalid input
   (e.g. day=32), while Date::Parse wraps these calls in eval and
   returns undef. Added input validation and eval guards to match.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All three review comments addressed:

- **P1 — Missing `-1` sentinel and overflow guards**: Added the `-1` sentinel check and `$result < 0` overflow guard to both the `timegm` and `timelocal` eval blocks, matching `Date::Parse::str2time` exactly (including the `localtime(-1)` approach for the `timelocal` path and the different year threshold: `>= 1970` for `timegm`, `>= 1971` for `timelocal`).
- **P2 — Unused `POSIX qw(mktime)` import**: Removed the unused import from `t/lang-str2time.t`.
- **P2 — Redundant `defined $month` guard**: Removed `defined $month &&` from the validation check since `$month` is always defined at that point.
@Koan-Bot
Copy link
Copy Markdown
Author

Rebase with requested adjustments

Branch koan.atoomic/fix-language-str2time was rebased onto main and review feedback was applied.

Changes applied

  • All three review comments addressed:
  • P1 — Missing -1 sentinel and overflow guards: Added the -1 sentinel check and $result < 0 overflow guard to both the timegm and timelocal eval blocks, matching Date::Parse::str2time exactly (including the localtime(-1) approach for the timelocal path and the different year threshold: >= 1970 for timegm, >= 1971 for timelocal).
  • P2 — Unused POSIX qw(mktime) import: Removed the unused import from t/lang-str2time.t.
  • P2 — Redundant defined $month guard: Removed defined $month && from the validation check since $month is always defined at that point.

Stats

2 files changed, 148 insertions(+), 4 deletions(-)
Actions performed
  • Already-solved check: negative (confidence=high, reasoning=None of the recent commits on main address Date::Language::str2time year inference or invalid input )
  • Rebased koan.atoomic/fix-language-str2time onto upstream/main
  • Applied review feedback
  • Pre-push CI check: previous run passed
  • Force-pushed koan.atoomic/fix-language-str2time to origin
  • CI check enqueued in ## CI (async)

CI status

CI will be checked asynchronously.


Automated by Kōan

@Koan-Bot Koan-Bot force-pushed the koan.atoomic/fix-language-str2time branch from cf2c050 to 72f7046 Compare April 27, 2026 05:57
@atoomic atoomic merged commit c9c0020 into cpan-authors:main Apr 29, 2026
22 checks passed
@Koan-Bot Koan-Bot deleted the koan.atoomic/fix-language-str2time branch April 29, 2026 10:21
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