From Code to Community: Sponsoring The Perl and Raku Conference 2025 Learn more

use Test::Most 0.25;
use List::Util 1.39 qw< pairs >; # minimum version for pairs returning objects
# local test modules
use Cwd 'abs_path';
use lib File::Spec->catdir(dirname(abs_path($0)), 'lib');
use DateEasyTestUtil qw< is_32bit compare_times >;
use DateParseTests qw< %DATE_PARSE_TESTS _date_parse_remove_timezone >;
use TimeParseDateTests qw< @TIME_PARSE_DATE_TESTS get_ymd_from_parsedate >;
# first go through stuff we handle specially: integers which may or not be interprested as epoch
# seconds, or else might be a datestring (that is, YYYYMMDD).
my %TEST_DATES =
(
1426446360 => '2015-03-15', # simple epoch
20120930 => '2012-09-30', # simple datestring
29000000 => '1970-12-02', # epoch (too big to be a datestring)
28991231 => '2899-12-31', # datestring (upper bound)
10000101 => '1000-01-01', # datestring (lower bound)
9999999 => '1970-04-26', # epoch (too small to be a datestring)
-99590400 => '1966-11-05', # epoch (negative)
);
my $t;
my $on_32bit_machine = is_32bit();
foreach (keys %TEST_DATES)
{
TODO:
{
my $expected = $TEST_DATES{$_};
local $TODO = "out of range for 32-bit machines"
if $on_32bit_machine and ( $expected le '1901-99-99' or $expected ge '2038-00-00' );
lives_ok { $t = date($_) } "parse survival: $_";
compare_times($t, $expected, "successful parse: $_");
}
}
# now rifle through everything that str2time can handle
# If our invocation of str2time (or, more accurately, str2time's guts) fails, our fallback will be
# such that it will just pass the parsing on to parsedate, which might very well succeed. However,
# for this loop, str2time should *not* fail, so we need to consider a parsedate success as a test
# failure. In order to achieve this, we're going to wrap Date::Easy::Date::_parsedate with a local
# closure that notifies us if the fallback triggers.
my $using_fallback;
{
no warnings 'redefine';
*Date::Easy::Date::_parsedate_orig = \&Date::Easy::Date::_parsedate;
*Date::Easy::Date::_parsedate = sub { $using_fallback = 1; &Date::Easy::Date::_parsedate_orig };
}
foreach (keys %DATE_PARSE_TESTS)
{
$using_fallback = 0; # always reset this before calling date() (see above)
lives_ok { $t = date($_) } "parse survival: $_";
# figure out what the proper date *should* be by dropping any timezone specifier
my $proper = _date_parse_remove_timezone($_);
compare_times($t, local => str2time($proper), "successful parse: $_")
or diag("compared against parse of: $proper");
is $using_fallback, 0, "parsed $_ without resorting to fallback";
}
# could undo our monkey patch here, but it isn't hurting anything, and we might find it useful later
# a few basic tests for the parsedate side of it
my $tomorrow = today + 1;
$t = date("tomorrow");
compare_times($t, $tomorrow, "successful parse: tomorrow");
# this one is known to be unparseable by str2time()
# (taken from MUIR/Time-ParseDate-2013.1113/t/datetime.t)
$t = date('950404 00:22:12 "EDT');
compare_times($t, '1995-04-04', "successful parse: funky datestring plus time");
# now rifle through everything that parsedate can handle
foreach (pairs @TIME_PARSE_DATE_TESTS)
{
my ($str, $orig_t, @args) = ( $_->key, @{ $_->value } );
# anything which str2date can successfully parse would be handled by it, not parsedate
# so skip those
next if defined str2time($str);
# If parsedate() won't parse this (e.g. because it requires PREFER_PAST or PREFER_FUTURE, which
# we're not going to supply, or because it's just expected to fail), skip this test.
next unless defined parsedate($str);
# If the only thing that would cause parsedate to fail is not having a date (e.g. "now +4 secs"),
# let's test that and make sure date() fails as well.
unless ( defined parsedate($str, DATE_REQUIRED => 1) )
{
throws_ok { date($str) } qr/Illegal date/, "correctly refused to parse: $str";
next;
}
# if we got this far, the parse shouldn't blow up
lives_ok { $t = date($str) } "parse survival: $str";
# and the date generated should be the same date that we would generate from the year, month,
# and day that we *would* get out of parsedate if it actually returned those things
# (since it doesn't, we have to use our hacky simulation of how that would work, if it worked;
# see get_ymd_from_parsedate in TimeParseDateTests)
my $d; lives_ok { $d = Date::Easy::Date->new(get_ymd_from_parsedate($str)) }
"[sanity check] test parse survival: $str";
compare_times($t, $d, "successful parse: $str");
}
# insure we properly handle a time of 0 (i.e. the exact day of the epoch)
my $local_epoch = timelocal gmtime 0; # for whatever timezone we happen to be in
foreach (
$local_epoch, # handled internally (epoch seconds)
'19700101', # handled internally (compact datestring)
'1970-1-1-00:00:00 GMT', # handled by Date::Parse
'1970/01/01 foo', # handled by Time::ParseDate (zero in UTC)
)
{
compare_times(date($_), UTC => 0, "successful 0 parse: $_");
}
# we need to deal with both 0 UTC and whatever actual day 0 local time is
# (however, local time can only return 0 differently than UTC in the case of Time::ParseDate)
foreach (
# handled by Time::ParseDate (zero in localtime)
Time::Piece->_mktime(0, 1)->strftime("%Y/%m/%d %H:%M:%S foo"),
)
{
compare_times(date($_), local => 0, "successful local 0 parse: $_");
}
done_testing;