Search code examples
perllocaltime

Perl function localtime giving incorrect values for years between 1964 and 1967


I was getting some whacky values from localtime function in Perl. The following is some code for which I get incorrect values.

In particular, this code is meant to determine the weekday for the first of each year.

#!/usr/bin/perl
use strict 'vars';
use Time::Local;
use POSIX qw(strftime);

mytable();

sub mytable {
   print "Year" . " "x4 .  "Jan 1st (localtime)" . " "x4 . "Jan 1st (Gauss)\n";
   foreach my $year ( 1964 .. 2017 )
     {
       my $janlocaltime = evalweekday( 1,1,$year);
       my $jangauss     = gauss($year);
       my $diff = $jangauss - $janlocaltime;
       printf "%4s%10s%-12s ",$year,"",$janlocaltime;
       printf "%12s",$jangauss;
       printf " <----- ERROR: off by %2s", $diff if ( $diff != 0 );
     print "\n";
     }
}

sub evalweekday {
   ## Using "localtime"
   my ($day,$month,$year) = @_;
   my $epoch   =  timelocal(0,0,0, $day,$month-1,$year-1900);
   my $weekday = ( localtime($epoch) ) [6];
   return $weekday;
 }

sub gauss {
   ## Alternative approach
   my ($year) = @_;
   my $weekday =
     (  1 + 5 * ( ( $year - 1 ) % 4 )
      + 4 * ( ( $year - 1 ) % 100 )
      + 6 * ( ( $year - 1 ) % 400 )
    ) % 7;
   return $weekday;
 }

Here is the output which shows the years with incorrect values:

Year    Jan 1st (localtime)    Jan 1st (Gauss)
1964          2                       3 <----- ERROR: off by  1
1965          4                       5 <----- ERROR: off by  1
1966          5                       6 <----- ERROR: off by  1
1967          6                       0 <----- ERROR: off by -6
1968          1                       1
1969          3                       3
1970          4                       4
1971          5                       5
1972          6                       6
1973          1                       1
1974          2                       2
1975          3                       3
1976          4                       4
1977          6                       6
1978          0                       0
1979          1                       1
1980          2                       2
1981          4                       4
1982          5                       5
1983          6                       6
1984          0                       0
1985          2                       2
1986          3                       3
1987          4                       4
1988          5                       5
1989          0                       0
1990          1                       1
1991          2                       2
1992          3                       3
1993          5                       5
1994          6                       6
1995          0                       0
1996          1                       1
1997          3                       3
1998          4                       4
1999          5                       5
2000          6                       6
2001          1                       1
2002          2                       2
2003          3                       3
2004          4                       4
2005          6                       6
2006          0                       0
2007          1                       1
2008          2                       2
2009          4                       4
2010          5                       5
2011          6                       6
2012          0                       0
2013          2                       2
2014          3                       3
2015          4                       4
2016          5                       5
2017          0                       0

In fact, the errors seem to extend as far back as 1900, but I just haven't verified that they are in fact wrong prior to 1964.

perl --version returns the following:

This is perl 5, version 18, subversion 2 (v5.18.2) built for darwin-thread-multi-2level
(with 2 registered patches, see perl -V for more detail)

Copyright 1987-2013, Larry Wall

Perl may be copied only under the terms of either the Artistic License or the
GNU General Public License, which may be found in the Perl 5 source kit.

Complete documentation for Perl, including FAQ lists, should be found on
this system using "man perl" or "perldoc perl".  If you have access to the
Internet, point your browser at http://www.perl.org/, the Perl Home Page.

I'm not sure whether it's relevant, but my operating system is macOS Sierra Version 10.12.3.

I've read through the documentation, but I don't see anything (or I'm being blind) regarding values returned prior to 1968. I've also tried to do a websearch but am not pulling up anything beyond the typical misunderstandings of array values and the numbering of months and days of the year.

Could someone help me out and explain what I'm getting wrong? Or, if this is an issue with my version of Perl, let me know what I can do to fix it.


Solution

  • This is likely to do with how negative epoch values are handled in Time::Local. Have a look at perldoc Time::Local #Negative-Epoch-Values

    On my Linux box (perl 5.20), your code demonstrates the issue nicely. If you print out the epoch value received, you will see the issue, namely that the epoch returned by timelocal becomes huge instead of more negative:

    Year       Epoch Jan 1st (localtime)     Jan 1st (Gauss)
    1964  2966342400 2                       3 <----- ERROR: off by  1
    1965  2997964800 4                       5 <----- ERROR: off by  1
    1966  3029500800 5                       6 <----- ERROR: off by  1
    1967  3061036800 6                       0 <----- ERROR: off by -6
    1968   -63185400 1                       1
    1969   -31563000 3                       3
    1970      -27000 4                       4
    1971    31509000 5                       5
    1972    63045000 6                       6
    

    Why don't you try using DateTime library instead:

    use DateTime;
    my $dt = DateTime->new(
        year   => 1966, # Real Year
        day    => 1,    # 1-31
        month  => 1,    # 1-12
        hour   => 0,    # 0-23
        second => 0,    # 0-59
    );
    print $dt->dow . "\n";
    
    6
    

    6 = Saturday which matches the Wikipedian view: Jan 1, 1966 (Saturday)