fix: str2time rejects epoch -1 (Dec 31, 1969 23:59:59 UTC)#112
Conversation
The epoch -1 detection logic compares parsed date components against a hardcoded string to distinguish the legitimate -1 return value from timegm/timelocal errors. However, since two-digit years are now normalized to four-digit years before the timegm call, the comparison always fails — the join produces "59592331111969" (year=1969) but the check expected "595923311169" (year=69). Fix: update the UTC check to use the 4-digit year string, and adjust the local-time check to add 1900 to the localtime(-1) year component. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Greptile SummaryThis PR fixes Confidence Score: 5/5Safe to merge — the logic fix is correct and well-tested; only minor style suggestions remain. Both the UTC and local-time path fixes are logically sound. The hardcoded string '59592331111969' is verifiable by inspection (ss=59, mm=59, hh=23, day=31, month=11, year=1969). The local-time fix correctly applies += 1900 to match the normalized $year. All remaining comments are P2. No files require special attention.
|
| Filename | Overview |
|---|---|
| lib/Date/Parse.pm | Correctly updates both UTC and local-time epoch-(-1) comparison strings to account for the 4-digit year normalization; one minor style concern (unconditional localtime(-1) call). |
| t/edge-cases.t | Adds 5 well-chosen tests covering ISO 8601, RFC 2822, 2-digit year, and space-separated UTC formats; local-time (else) branch for epoch -1 is not directly tested. |
Reviews (1): Last reviewed commit: "fix: str2time rejects epoch -1 (Dec 31, ..." | Re-trigger Greptile
| my @_neg1 = localtime(-1); | ||
| $_neg1[5] += 1900; |
There was a problem hiding this comment.
localtime(-1) now called unconditionally
localtime(-1) is called on every execution of the local-time branch, whereas the original code only called it lazily (short-circuit evaluation, only when $result == -1). This is a negligible overhead in practice, but architecturally the call would be cleaner kept inline as before — the only change needed is adding + 1900 to the join.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| # --- Epoch -1 (Dec 31, 1969 23:59:59 UTC) parsing --- | ||
| # timegm() returns -1 for this date, which collides with Perl's error | ||
| # convention. str2time must distinguish the legitimate -1 from errors. | ||
| { | ||
| my $t = str2time("31 Dec 1969 23:59:59 GMT"); | ||
| is($t, -1, "epoch -1: 4-digit year with GMT"); | ||
|
|
||
| my $t2 = str2time("1969-12-31T23:59:59Z"); | ||
| is($t2, -1, "epoch -1: ISO 8601 with Z"); | ||
|
|
||
| my $t3 = str2time("Wed, 31 Dec 1969 23:59:59 +0000"); | ||
| is($t3, -1, "epoch -1: RFC 2822 with +0000"); | ||
|
|
||
| my $t4 = str2time("31 Dec 69 23:59:59 GMT"); | ||
| is($t4, -1, "epoch -1: 2-digit year with GMT"); | ||
|
|
||
| my $t5 = str2time("1969-12-31 23:59:59 +0000"); | ||
| is($t5, -1, "epoch -1: ISO with space separator"); | ||
| } | ||
|
|
||
| done_testing; |
There was a problem hiding this comment.
Local-time path not covered by new tests
All five new tests specify an explicit UTC offset (GMT, Z, +0000), so they exercise only the if (defined $zone) branch. The local-time else branch fix ($_neg1[5] += 1900) is not exercised by any of them. Consider adding a test without a timezone specifier to cover that code path.
What
str2timereturnsundeffor any date string representing epoch -1 (December 31, 1969 23:59:59 UTC).Why
The epoch -1 detection logic compares
join("", $ss, $mm, $hh, $day, $month, $year)against a hardcoded reference string to distinguish the legitimate-1return fromtimegm/timelocalerrors. Since the year normalization code (two-digit → four-digit) was added,$yearis always1969at the comparison point — but the reference string still used69. The join produces"59592331111969"(14 chars) while the check expected"595923311169"(12 chars), so the comparison always fails andstr2timeincorrectly returnsundef.How
"595923311169"to"59592331111969"localtime(-1)returns year=69 (years since 1900), so added+= 1900to match the normalized$yearTesting
t/edge-cases.tcovering ISO 8601, RFC 2822, 2-digit year, and space-separated formats🤖 Generated with Claude Code
Quality Report
Changes: 2 files changed, 24 insertions(+), 2 deletions(-)
Code scan: clean
Tests: skipped
Branch hygiene: clean
Generated by Kōan post-mission quality pipeline