houseabsolute / Time-Local

Efficiently compute time from local and GMT time
https://metacpan.org/release/Time-Local/
Other
4 stars 11 forks source link

After 2018-DEC-31T23:59:59, Time::Local::timelocal returns wrong value for dates before 1970-01-01T00:00:00 #8

Closed jlholt closed 5 years ago

jlholt commented 5 years ago

The timelocal function returns the wrong value as soon as the system clock reaches 01-JAN-2019T00:00:00.000000. But it apparently only does so for time values that precede 01-JAN-1970T00:00:00.000000.

I'm using Time::Local v1.28 on Perl v5.28.1 (built from source) on macos v10.14.2.

To execute the test, you install perl and Time::Local. Then you execute the following tl.sh, which does 3 things:

  1. prints perl path and perl version
  2. changes system date to 31-DEC-2018T23:59:57
  3. executes a loop printing date and running the tl.pl perl script.

Note that I expect tl.pl to exit with 1 as I stated above. If you cannot reproduce the failure, then you'll have to interrupt the execution once it becomes clear you won't reproduce the results. Also note that the only reason I've created this particular test is to show when in time the test starts to fail. Obviously, it will always fail for these old dates now that the clock is into 2019 but I thought it important for the module's author to know when in time it started to fail.

This is tl.sh:

which perl perl -V:version sudo date 123123592018.57 while [ 1 ]; do date perl tl.pl > t.out 2>&1 if [ $? -ne 0 ]; then cat t.out rm t.out break fi done

This is tl.pl:

use Time::Local;

my @tests = ( {name=>"1969-08-16T14:00:00", expect=>"-11854800", tlargs=>['00', '00', 14, '16', 07, 69, undef]}, {name=>"1969-08-18T03:00:00", expect=>"-11721600", tlargs=>['00', '00', 03, '18', 07, 69, undef]}, {name=>"1969-12-31T23:59:59", expect=>"21599", tlargs=>['59', '59', 23, '31', 11, 69, undef]}, {name=>"1970-01-01T00:00:00", expect=>"21600", tlargs=>['00', '00', 00, '01', 00, 70, undef]}, ); my $fail = 0; for (@tests) { my $test = $->{name}; my $expect = $->{expect}; my $got = timelocal(@{$_->{tlargs}}); my $result = $expect eq $got ? "OK" : "FAIL"; print "$test: $result expect='$expect' got='$got'\n"; $fail = 1 if $result ne "OK"; } exit ($fail ? 1 : 0);

When I execute tl.sh (after resetting the clock to the current date&time), I get the following consistently on my MacBook Pro running Mojave (10.14.2):

$ sh tl.sh /usr/local/bin/perl version='5.28.1'; Mon Dec 31 23:59:57 CST 2018 Mon Dec 31 23:59:57 CST 2018 ... lines elided Mon Dec 31 23:59:58 CST 2018 Mon Dec 31 23:59:58 CST 2018 ... lines elided Mon Dec 31 23:59:59 CST 2018 Mon Dec 31 23:59:59 CST 2018 ... lines elided Mon Dec 31 23:59:59 CST 2018 Tue Jan 1 00:00:00 CST 2019 1969-08-16T14:00:00: FAIL expect='-11854800' got='3143905200' 1969-08-18T03:00:00: FAIL expect='-11721600' got='3144038400' 1969-12-31T23:59:59: FAIL expect='21599' got='3155781599' 1970-01-01T00:00:00: OK expect='21600' got='21600'

autarch commented 5 years ago

You're being bitten by this - https://metacpan.org/pod/Time::Local#Year-Value-Interpretation

Use timelocal_modern and timegm_modern and pass them 4 digit years to avoid this madness.

jlholt commented 5 years ago

I'm actually logging this bug as a courtesy to Graham Barr, whose Date::Parse uses your Time::Local::timelocal function. Can you contact him about fixing his code? I logged bug 128158 but when I thought I found the source of the problem (your module), I sent an email asking for 128158 to be closed. That was a mistake given your feedback but I failed miserably when I tried to add an update for the bug. The rt site is a disgrace.

autarch commented 5 years ago

I doubt that Date::Parse can be changed either. It's an old module with lots of legacy code relying on its current behavior. I'd suggest using something like DateTime or Time::Moment.

jlholt commented 5 years ago

I'd love to use something else but I need something that parses dates, especially ISO 8601 date time values like the ones mentioned in the title of this case.

jlholt commented 5 years ago

Perhaps DateTime::Format::ISO8601 is all I need for parsing such dates?

autarch commented 5 years ago

That would probably work. If not, DateTime::Format::Natural will parse many many formats.

jlholt commented 5 years ago

DateTime::Format::Natural does not handle ISO 8601 "Combined date and time representations".

$ cat t.pl use DateTime::Format::Natural;

my $s1 = "1999-01-01T00:00:00"; my $dtparser = DateTime::Format::Natural->new(); my $dt = $dtparser->parse_datetime($s1); die $dtparser->error if ! $dtparser->success;

$ perl t.pl '1999-01-01T00:00:00' does not parse (perhaps you have some garbage?) at t.pl line 6.

But it's not that big of deal to first try DateTime::Format::ISO8601 and fall back upon DateTime::Format::Natural if necessary.