Skip to content

fix: make jcpan -t DateTimeX::Easy pass (regex + warnings + Time::HiRes/Piece fixes)#560

Merged
fglock merged 7 commits intomasterfrom
feature/jcpan-datetimex-easy
Apr 25, 2026
Merged

fix: make jcpan -t DateTimeX::Easy pass (regex + warnings + Time::HiRes/Piece fixes)#560
fglock merged 7 commits intomasterfrom
feature/jcpan-datetimex-easy

Conversation

@fglock
Copy link
Copy Markdown
Owner

@fglock fglock commented Apr 25, 2026

Summary

Six fixes that together make jcpan -t DateTimeX::Easy exit 0 (was completely
blocked on a regex compilation error). Plan and progress tracked in
dev/modules/jcpan_datetimex_easy.md.

# Fix Why
1 CORE::GLOBAL::sleep override sleep was missing from OVERRIDABLE_OP; Test::MockTime::HiRes couldn't intercept sleep
2 runtime warning-scope after BEGIN { unimport warnings ... } parseSpecialBlock now emits a CompilerFlagNode after BEGIN so local ${^WARNING_SCOPE} = N propagates to runtime — needed by Module::Util and any module that uses BEGIN { unimport warnings 'cat' } inside a sub
3 duplicate named captures + /i name protection (?<x>a)|(?<x>b) is legal Perl but Java rejects duplicates; preprocessor now suffixes second-and-later occurrences with ZpjdupZ<N> and HashSpecialVariable groups them back together for %+/%-. Also stops /i multi-char fold expansion from corrupting capture-group names ((?<off>...)/i was becoming (?<o(?:ff|fi)>...))
4 s/\G.../.../ honors pos() substitution scanned from offset 0 even when the pattern uses \G and the previous /g match left pos() set; DateTime::Format::Natural::Rewrite relies on this idiom
5 Time::HiRes::gettimeofday in scalar context returned just microseconds instead of seconds + micros/1_000_000
6 Time::Piece->strptime lenient parsing rejected non-zero-padded numeric fields ("1:13:8" against "%H:%M:%S")

Effect on jcpan -t DateTimeX::Easy

Dist Before After
DateTime::Format::DateManip 0 tests run, regex compile fail 7 tests PASS
DateTime::Set PASS 959 tests PASS
Module::Util 1/47 fail 47 tests PASS
Test::MockTime::HiRes 7/13 fail 216 tests PASS
DateTime::Format::Natural 23/28 programs fail 11077 tests PASS
DateTimeX::Easy itself cascade fail 107 tests PASS

jcpan -t DateTimeX::Easy now exits 0.

Test plan

  • make green (all parallel unit shards pass)
  • New unit tests added:
    • src/test/resources/unit/operator_overrides.tsleep override
    • src/test/resources/unit/warnings.t — BEGIN-unimport-warnings inside sub
    • src/test/resources/unit/regex/regex_named_capture.t — duplicate names + /i name fold
    • src/test/resources/unit/regex/regex_g_pos.t\G in s/// honors pos()
  • jcpan -t DateTimeX::Easy exits 0 from a clean build directory

Generated with Devin

fglock and others added 7 commits April 25, 2026 21:12
`sleep` was missing from the OVERRIDABLE_OP list, so user code installing
`*CORE::GLOBAL::sleep = sub { ... }` was silently bypassed and the native
`sleep` ran instead. Adding it lets Test::MockTime::HiRes (and any other
time-mocking module) intercept `sleep` to advance a mocked clock without
actually waiting.

Also adds a new `sleep operator override` subtest in operator_overrides.t
covering this code path, and adds dev/modules/jcpan_datetimex_easy.md
tracking the broader DateTimeX::Easy install plan.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
`BEGIN { unimport warnings qw(Cat) }` inside a sub body sets
WarningFlags.lastScopeId at compile time, but parseSpecialBlock returned a
plain `undef` node and never emitted a CompilerFlagNode. The result was that
the surrounding lexical scope did not get `local ${^WARNING_SCOPE} = N`
emitted at runtime, so warnings::warnif() did not honor the suppression.

Mirror the tail of parseUseDeclaration: after a BEGIN block runs, capture
the current symbol-table flags + lastScopeId into a CompilerFlagNode so the
runtime sees the change. This is the idiom Module::Util uses to silence
File::Find's "Can't stat" warning when given a non-existent search path,
and it unblocks Module::Util's t/01..module.t test 44.

Adds a unit test in src/test/resources/unit/warnings.t covering the
File::Find use case.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Two related preprocessor bugs that together blocked Date::Manip and any
CPAN module that uses Perl 5.10+ duplicate-name capture groups:

1. `(?<x>a)|(?<x>b)` is legal Perl but Java rejects "Named capturing
   group <x> is already defined". Now RegexPreprocessor tracks names it
   has emitted and suffixes the second-and-later occurrences with a
   distinctive marker (`ZpjdupZ<N>`); CaptureNameEncoder.decodeGroupName
   strips the marker so user code sees the original name in `%+`/`%-`.
   HashSpecialVariable groups the duplicates back together so
   `$+{name}` returns the matched alternative and `$-{name}` returns
   an arrayref of all alternatives (matching Perl's semantics).

2. With /i, `expandMultiCharFolds` was rewriting characters inside
   `(?<name>...)` group names — e.g. `(?<off>...)/i` was turning into
   `(?<o(?:ff|fi)>...)`, which Java rejects. Group names are syntactic
   identifiers, not pattern text, so the preprocessor now skips over
   the name portion of named captures (`(?<name>`, `(?'name'`,
   `(?P<name>`, `(?P=name)`) before applying fold expansion.

Together these unblock Date::Manip-based modules
(DateTime::Format::DateManip, DateTime::Format::Natural via Date::Manip,
etc.) and many other CPAN modules that rely on the duplicate-name idiom.

Adds 6 subtests to regex/regex_named_capture.t covering both fixes.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Issues A (duplicate named captures), C (silent missing-path /
warning-scope after BEGIN), and D (CORE::GLOBAL::sleep) are done.
Issue B (DateTime::Format::Natural parse_success failures) remains
as planned follow-up.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
`s/\G.../.../` was scanning from offset 0 even when pos() was set by a
previous /g match. As a result, an idiom like

    $s =~ /pattern/g;     # leaves pos($s) at end of match
    $s =~ s/\G/:00/;      # should insert at pos(), not at 0

prepended the replacement to the start of the string instead of
appending at the previous match end. DateTime::Format::Natural relies
on this idiom to rewrite "feb 28 at 3" into "feb 28 at 3:00" before
parsing.

replaceRegex() now mirrors matchRegex(): when the pattern uses \G it
looks up RuntimePosLvalue.pos(string) and, if defined, sets the
matcher's region to start there (with useAnchoringBounds(false) so
^/$ in /m don't anchor at the artificial boundary).

Adds a unit test in regex/regex_g_pos.t.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Two small fixes that together let DateTime::Format::Natural's
mock-time tests run, which was the last missing piece for
`jcpan -t DateTimeX::Easy` to pass end-to-end.

* `Time::HiRes::gettimeofday` in scalar/void context returned just the
  microseconds component (e.g. `924972`) instead of the floating-point
  epoch (`1777146550.92...`). It now mirrors real Perl: scalar context
  yields `seconds + micros / 1_000_000`, list context still yields the
  `(seconds, microseconds)` integer pair.

* `Time::Piece->strptime` was rejecting non-padded numeric fields
  ("1:13:8" against "%H:%M:%S") because Java's `HH`/`mm`/`ss` require
  exactly two digits. Switched the formatter to a lenient builder
  (`DateTimeFormatterBuilder.parseLenient().appendPattern(...)`) so
  POSIX-style "1:13:8" now parses, matching real Perl.

`jcpan -t DateTimeX::Easy` now exits 0:

  DateTime::Format::DateManip   7 tests   PASS
  DateTime::Set                959 tests  PASS
  Module::Util                  47 tests  PASS
  Test::MockTime::HiRes        216 tests  PASS
  DateTime::Format::Natural  11077 tests  PASS
  DateTimeX::Easy              107 tests  PASS

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
`jcpan -t DateTimeX::Easy` now passes (107/107). Updated the progress
section with all six commits and the final test counts.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@fglock fglock changed the title fix: unblock most of jcpan -t DateTimeX::Easy (regex dup names, BEGIN warn-scope, sleep override) fix: make jcpan -t DateTimeX::Easy pass (regex + warnings + Time::HiRes/Piece fixes) Apr 25, 2026
@fglock fglock merged commit 0663a80 into master Apr 25, 2026
2 checks passed
@fglock fglock deleted the feature/jcpan-datetimex-easy branch April 25, 2026 20:46
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.

1 participant