diff --git a/lib/Date/Language.pm b/lib/Date/Language.pm index 17570f4..4af31b6 100644 --- a/lib/Date/Language.pm +++ b/lib/Date/Language.pm @@ -79,13 +79,16 @@ sub str2time return undef unless @t; - my($ss,$mm,$hh,$day,$month,$year,$zone) = @t; + my($ss,$mm,$hh,$day,$month,$year,$zone,$century) = @t; my @lt = localtime(time); $hh ||= 0; $mm ||= 0; $ss ||= 0; + my $frac = $ss - int($ss); + $ss = int $ss; + $month = $lt[4] unless(defined $month); @@ -98,6 +101,14 @@ sub str2time $year = $is_future ? ($lt[5] - 1) : $lt[5]; } + # Restore 4-digit year when century was present in the input + $year += 1900 if defined $century; + + # Normalize two-digit years using POSIX convention (match Date::Parse behavior). + # Without this, Time::Local's own sliding window produces different results than + # Date::Parse::str2time for the same input string. + $year += ($year >= 69 ? 1900 : 2000) if $year < 100; + return undef unless($month <= 11 && $day >= 1 && $day <= 31 && $hh <= 23 && $mm <= 59 && $ss <= 59); @@ -132,7 +143,7 @@ sub str2time return undef if $result < 0 && $year >= 1971; } - return $result; + return $result + $frac; } 1; diff --git a/t/lang-str2time.t b/t/lang-str2time.t index 658ed48..d7cc7d3 100644 --- a/t/lang-str2time.t +++ b/t/lang-str2time.t @@ -105,4 +105,60 @@ my $lang = Date::Language->new('English'); is($parsed, $t, "German round-trip ctime/str2time"); } +# --- Two-digit year normalization: must match Date::Parse --- +# Without explicit normalization, Date::Language relies on Time::Local's +# sliding window which diverges from Date::Parse's fixed POSIX threshold. + +{ + for my $y (69, 70, 75, 95, 99) { + my $date = "1 Jun $y GMT"; + my $dp = str2time($date); + my $dl = $lang->str2time($date); + ok(defined $dp, "Date::Parse parses '1 Jun $y GMT'"); + ok(defined $dl, "Date::Language parses '1 Jun $y GMT'"); + if (defined $dp && defined $dl) { + my $dp_year = (gmtime($dp))[5] + 1900; + my $dl_year = (gmtime($dl))[5] + 1900; + is($dl_year, $dp_year, + "two-digit year $y: Language ($dl_year) matches Parse ($dp_year)"); + } + } + + # Also test years in the 2000s range + for my $y (0, 1, 26, 50, 68) { + my $date = "15 Mar $y GMT"; + my $dp = str2time($date); + my $dl = $lang->str2time($date); + ok(defined $dp, "Date::Parse parses '15 Mar $y GMT'"); + ok(defined $dl, "Date::Language parses '15 Mar $y GMT'"); + if (defined $dp && defined $dl) { + my $dp_year = (gmtime($dp))[5] + 1900; + my $dl_year = (gmtime($dl))[5] + 1900; + is($dl_year, $dp_year, + "two-digit year $y: Language ($dl_year) matches Parse ($dp_year)"); + } + } +} + +# --- Four-digit year with century: must survive round-trip --- + +{ + my $date = "15 Mar 2024 10:30:00 GMT"; + my $dp = str2time($date); + my $dl = $lang->str2time($date); + ok(defined $dp && defined $dl, "4-digit year parses in both"); + is($dl, $dp, "4-digit year: Language matches Parse exactly"); +} + +# --- Fractional seconds preserved --- + +{ + my $date = "2024-01-15T12:34:56.789 GMT"; + my $result = $lang->str2time($date); + ok(defined $result, "fractional seconds date parses"); + my $frac = $result - int($result); + ok($frac > 0.78 && $frac < 0.80, + "fractional seconds preserved (got $frac, expected ~0.789)"); +} + done_testing;