Date-Cmp
view release on metacpan or search on metacpan
t/function.t view on Meta::CPAN
is(silence_stderr { datecmp('BEF. 1932', '2005-06-16') }, $LT,
'BEF. 1932 < 2005-06-16');
# Cases the code explicitly cannot handle fall back to 0 without dying
is(silence_stderr { datecmp('aft 1900', 'BET 1890 AND 1910') }, $EQ,
'unhandled aft/BET combo returns 0');
};
# =========================================================================
# 10. Approximate prefix stripping â left side
# "Abt.", "Abt", "ca.", "ca", and trailing "?" must all be stripped to
# expose the underlying year for numeric comparison.
# =========================================================================
subtest 'approximate prefix stripping on left side' => sub {
is(datecmp('Abt. 1900', '1900'), $EQ, 'Abt. 1900 == 1900');
is(datecmp('Abt 1900', '1900'), $EQ, 'Abt (no dot) == 1900');
is(datecmp('ca. 1900', '1900'), $EQ, 'ca. 1900 == 1900');
is(datecmp('ca 1900', '1900'), $EQ, 'ca 1900 == 1900');
is(datecmp('1900 ?', '1900'), $EQ, 'trailing ? stripped');
cmp_ok(datecmp('Abt. 1899', '1900'), '<', $EQ, 'Abt. 1899 < 1900');
cmp_ok(datecmp('ca 1901', '1900'), '>', $EQ, 'ca 1901 > 1900');
};
# =========================================================================
# 11. Month-range stripping â left side
# "Oct/Nov/Dec YYYY" and similar slash-separated month lists must be
# reduced to the year component before comparison.
# =========================================================================
subtest 'month range stripped to year on left side' => sub {
is(datecmp('Oct/Nov/Dec 1950', '1950'), $EQ, 'Oct/Nov/Dec 1950 == 1950');
cmp_ok(datecmp('Oct/Nov/Dec 1949', '1950'), '<', $EQ,
'Oct/Nov/Dec 1949 < 1950');
};
# =========================================================================
# 12. Left-side "or" range
# "1802 or 1803" uses the first (start) year. If both years are the same
# the optional complain callback must be invoked.
# =========================================================================
subtest 'left-side "or" range uses start year and fires complain when equal' => sub {
cmp_ok(datecmp('1802 or 1803', '1801'), '>', $EQ, '1802 or 1803 > 1801');
cmp_ok(datecmp('1802 or 1803', '1804'), '<', $EQ, '1802 or 1803 < 1804');
# The RHS must share the same year as the "or" range so the fast-path
# year comparison ties and falls through to the "or" branch. Using a
# different RHS year (e.g. 1803) would cause the fast path to return
# immediately and the complain callback would never be reached.
my $complaint;
datecmp('1802 or 1802', '1802', sub { $complaint = $_[0] });
like($complaint, qr/the years are the same/, 'complain fires for same-year "or"');
};
# =========================================================================
# 13. Left-side date range (dash and BET forms)
# A range [from, to] on the LHS is compared with a scalar year on the RHS.
# - year < from â range returns +1 (range is later)
# - year in [from,to] â 0
# - year > to â range returns -1 (range is earlier)
# An inverted range (from > to) triggers the complain callback and returns 0.
# Equal endpoints collapse to a single year and also fire complain.
# =========================================================================
subtest 'left-side date range comparison (dash and BET forms)' => sub {
# Dash form
is(datecmp('1900-1902', '1899'), $GT, 'dash range > year before start');
is(datecmp('1900-1902', '1900'), $EQ, 'dash range == start year');
is(datecmp('1900-1902', '1901'), $EQ, 'dash range == mid year');
is(datecmp('1900-1902', '1902'), $EQ, 'dash range == end year');
is(datecmp('1900-1902', '1903'), $LT, 'dash range < year after end');
# BET ⦠AND ⦠form must be equivalent for every case
is(datecmp('BET 1900 AND 1902', '1899'), $GT, 'BET > year before');
is(datecmp('BET 1900 AND 1902', '1901'), $EQ, 'BET == mid year');
is(datecmp('BET 1900 AND 1902', '1903'), $LT, 'BET < year after');
is(datecmp('1830-1832', '1831'),
datecmp('BET 1830 AND 1832', '1831'),
'dash and BET forms give identical results');
# Inverted range (from > to): complain, return 0
my $inv_complaint;
is(silence_stderr { datecmp('1902-1900', '1901', sub { $inv_complaint = $_[0] }) },
$EQ, 'inverted dash range returns 0');
like($inv_complaint, qr/\d+ > \d+/, 'inverted range fires complain');
# Same endpoints: collapse to that year, fire complain
my $eq_complaint;
is(datecmp('1900-1900', '1900', sub { $eq_complaint = $_[0] }),
$EQ, 'same-endpoint range == that year');
like($eq_complaint, qr/from == to/, 'same-endpoint range fires complain');
};
# =========================================================================
# 14. BEF qualifier on right side
# =========================================================================
subtest 'BEF qualifier on right side' => sub {
# Plain integer LHS < bef-year on RHS â -1
is(datecmp(1939, 'bef 1 Jun 1965'), $LT, '1939 < bef 1 Jun 1965');
# The "Before not handled" fallback only fires when $left is not a plain
# integer (i.e. it is a DateTime ref after DFG parsing), because the code
# first checks "$left =~ /^\d+$/" and a ref does not satisfy that.
# Using the same year forces all fast-path ties so DFG is reached.
is(silence_stderr { datecmp('1 Jan 1900', 'bef 1900') }, $EQ,
'unhandled BEF on RHS with DateTime LHS returns 0');
};
# =========================================================================
# 15. Approximate prefix stripping â right side
# =========================================================================
subtest 'approximate prefix stripping on right side' => sub {
is(datecmp('1900', 'Abt. 1900'), $EQ, 'Abt. stripped on RHS');
is(datecmp('1900', 'ca. 1900'), $EQ, 'ca. stripped on RHS');
is(datecmp('1900', 'ca 1900'), $EQ, 'ca (no dot) stripped on RHS');
is(datecmp('1900', '1900 ?'), $EQ, 'trailing ? stripped on RHS');
cmp_ok(datecmp('1901', 'ca 1900'), '>', $EQ, '1901 > ca 1900 after strip');
};
# =========================================================================
# 16. Month-range stripping â right side
# =========================================================================
subtest 'month range stripped to year on right side' => sub {
is(datecmp('1892', 'Oct/Nov/Dec 1892'), $EQ, 'RHS month range == year');
cmp_ok(datecmp('1891', 'Oct/Nov/Dec 1892'), '<', $EQ,
'year < RHS month-range year');
};
# =========================================================================
# 17. Right-side date range (dash and BET forms)
# Same semantics as section 13 but with the range on the RHS.
# =========================================================================
subtest 'right-side date range comparison (dash and BET forms)' => sub {
is(datecmp(1899, 'BET 1900 AND 1902'), $LT, 'year before range < range');
is(datecmp(1900, 'BET 1900 AND 1902'), $EQ, 'start year == range');
is(datecmp(1901, 'BET 1900 AND 1902'), $EQ, 'mid year == range');
is(datecmp(1902, 'BET 1900 AND 1902'), $EQ, 'end year == range');
is(datecmp(1903, 'BET 1900 AND 1902'), $GT, 'year after range > range');
is(datecmp(1831, '1830-1832'), $EQ, 'mid year == dash range');
is(datecmp(1829, '1830-1832'), $LT, 'year before dash range');
is(datecmp(1833, '1830-1832'), $GT, 'year after dash range');
is(datecmp(1831, '1830-1832'),
datecmp(1831, 'BET 1830 AND 1832'),
'RHS dash and BET forms are equivalent');
# Equal endpoints collapse to a single year and fire complain
my $complaint;
is(datecmp(1900, '1900-1900', sub { $complaint = $_[0] }),
$EQ, 'same-endpoint RHS range == that year');
like($complaint, qr/from == to/, 'same-endpoint RHS range fires complain');
};
# =========================================================================
# 18. Regression â DateTime object on LHS must be unwrapped before the
# right-side range comparison.
#
# Root cause: '1 Jan 1996' has its first \d{3,4} sequence as '1996',
# which equals the range start so the early-exit fast path does NOT fire.
# The string then falls through to DFG parsing and $left becomes a
# DateTime object. The range handler's "$left == $to" comparison must
# unwrap $left first, or DateTime's overloaded == dies on a plain integer.
# =========================================================================
subtest 'DateTime LHS does not crash when compared against a year range' => sub {
is(datecmp('1 Jan 1996', '1996-2000'), $EQ, 'DateTime LHS within dash range');
is(datecmp('1 Jan 1996', 'BET 1996 AND 2000'), $EQ, 'DateTime LHS within BET range');
cmp_ok(datecmp('1 Jan 1994', '1996-2000'), '<', $EQ, 'DateTime LHS before dash range');
cmp_ok(datecmp('1 Jan 2001', '1996-2000'), '>', $EQ, 'DateTime LHS after dash range');
};
# =========================================================================
# 19. DFG parsing path â controlled via mocked $dfg singleton
#
# We replace $Date::Cmp::dfg with a fresh MockDFG for each sub-case so
# queue state never leaks between assertions.
#
# Call-count verification uses MockDFG's built-in counter rather than
# Test::Mockingbird's spy(), which does not reliably intercept OO dispatch
# when the invocant is a non-DFG class installed via "local".
#
# For the failure paths we use Test::Mockingbird's mock() to override
# DateTime::Format::Genealogy::parse_datetime globally (and restore it
# with unmock() afterwards) so the real $dfg singleton returns nothing.
# =========================================================================
subtest 'DFG parsing paths (mocked $dfg)' => sub {
# --- Left side parsed by DFG, right is a plain year ---
# '1 Jan 1900' falls through all fast paths because its first \d{3,4}
# match is 1900, which ties with the plain '1900' on the right; DFG
# is therefore called for the left side and the returned year is used.
{
my $mock = MockDFG->new();
local $Date::Cmp::dfg = $mock;
$mock->enqueue([ Fake::DateTime->new(year => 1900) ]);
is(datecmp('1 Jan 1900', '1900'), $EQ, 'DFG LHS year 1900 == plain 1900');
}
{
my $mock = MockDFG->new();
local $Date::Cmp::dfg = $mock;
$mock->enqueue([ Fake::DateTime->new(year => 1899) ]);
is(datecmp('1 Jan 1899', '1900'), $LT, 'DFG LHS year 1899 < plain 1900');
}
# --- Both sides parsed by DFG â final $left <=> $right via overloading ---
# '1 Jan 1900' and '1 Feb 1900' share the same embedded year so every
# fast-path numeric tie falls through. After the left is replaced by a
# Fake::DateTime that stringifies to "1900", the right-side fallback also
# sees a tie and sends '1 Feb 1900' to DFG. The mock returns a later
# year for the right side; MockDFG.call_count() tracks invocations.
{
my $mock = MockDFG->new();
local $Date::Cmp::dfg = $mock;
( run in 0.637 second using v1.01-cache-2.11-cpan-9789f410c06 )