itis.guptak has asked for the wisdom of the Perl Monks concerning the following question:

Hi Everyone, I'm trying to debug a 1000 line perl code written by someone else. I run this code through a cron job. It works but with an error. The problem is with the timelocal function. I think the values I'm passing to the timelocal function are valid. Like the month is 0 for Jan etc. Day '29' out of range 1..28 Could you please help me find the reason for this error.

Replies are listed 'Best First'.
Re: Timelocal out of range message
by kcott (Archbishop) on Jan 29, 2014 at 07:23 UTC

    G'day itis.guptak,

    Welcome to the monastery.

    Please provide a short script that reproduces your problem. How to do this is explained in "How do I post a question effectively?".

    Without seeing any code or data, we are not able to "find the reason for this error". However, the only instance I can think of giving "Day '29' out of range 1..28" would be during February of a non-leap year.

    Please ensure you haven't made a simple syntax error (e.g. number and order of timelocal() arguments) by checking the Time::Local documentation.

    -- Ken

      Thanks, Ken. Let me just explain what this script does. It pulls the systems message file from a list of hosts and put the content in a single file output on a central server from where it's run as cron job. I'm pasting a portion of code that's responsible for timelocal() execution in the script. The code I'm pasting contains three subroutines: addMessage(), processFile(), getLogsSCP(). getLogsSCP() will scp the files to central server. processFile() is called within getLogsSCP() and processFile() furthur calls addMessage(). Finally it's the addMessage() that invokes timelocal(). I've also pasted some variables used for computing the parameters passed to the timelocal function. This line of the code is generating the error.

      my $logtime = timelocal(${sec}, ${min}, ${hour}, ${mday}, ${mon}, ${current_year} - ${new_yea +r});

      Code

      sub getLogsSCP { my @hosts = @_; dprint "Getting logs for the last ${cutoff} hours"; dprint " from " . @hosts . " hosts\n"; # Build command to list log files # Using find allows us to scan only files that are new enough my $ssh_command = "find"; $ssh_command .= " @messages_files"; $ssh_command .= " -mtime -" . (int(${cutoff} / 24) + 1); my $nHosts = 1; foreach (@hosts) { chomp; my $host = $_; $current_host = ${host}; $last{${host}} = ""; my $c = 0; while (<READER>) { chomp; my $file = $_; my $tmpfile = "/tmp/logmon.${host}.$hnow{year}$hnow{mon}$hnow{md +ay}.$ENV{LOGNAME}"; # Copy log file dprint localtime() . " Copying ${file} to ${tmpfile}\n"; my $scp = Net::SCP->new({"host" => ${host}}); $scp->get(${file}, ${tmpfile}); # Process log file my $size = (stat("${tmpfile}"))[7]; if (! defined(${size})) { dprint localtime() . " Failed to copy ${file} from ${host}\n"; addFakeMessage(${host}, "FAILED TO COPY ${file} FROM ${host}") +; $total_count++; next; } dprint localtime() . " Parsing ${tmpfile} (${size} bytes)\n"; open(FD, "${tmpfile}") or next; unlink "${tmpfile}"; $c += processFile(\*FD, ${tmpfile}, ${host}); close(FD); } push @emptyhosts, ${host} if ($c == 0); $total_count += $c; close(*READER); close(*WRITER); close(*ERR); } dprint "\n"; } # Process a log file (or stream) sub processFile() { my $FD = shift; # Caller must open and close file descrip +tor my $file = shift; my $host = shift; my $c = 0; my $line = 1; while (<FD>) { # Log file is too big if ($line++ > ${max_messages}) { dprint "\n" . localtime() . " Too many messages in ${file}"; addFakeMessage(${host}, "STOPPED PROCESSING ${file} AFTER ${max_ +messages} MESSAAGES (please rotate)"); $total_count++; last; } dprint "${line}..." if (($line % 10000) == 0); my $retval += addMessage($_); if (${retval} < 0) { $c = 0; } else { $c += ${retval}; } } dprint "\n" if (${line} > 10000); return ${c} ; } sub addMessage($) { my $msg = shift; chomp ${msg}; # Skip blank lines if (${msg} =~ /^$/) { $reason = "blank"; return 0; } # Parse message my ($month, $mday, $tm, $host, @message) = split(/ ?/, ${msg}); my ($hour, $min, $sec) = split(/:/, ${tm}) if (defined(${tm})); # Skip line if we don't know the hostname if (! defined(${host}) || ${host} eq "") { $reason = "hostname" ; re +turn 0; } # Skip lines that don't look like syslog messages unless (${tm} =~ /[0-2]\d:[0-5]\d/) { $reason = "tm = ${tm}"; return + 0; } # Skip lines that are too old my $mon = $month_to_mon{${month}}; my $logtime = timelocal(${sec}, ${min}, ${hour}, ${mday}, ${mon}, ${current_year} - ${new_yea +r}); <----------------- This line is producing the error. if ((${mon} == 0) && (${mday} == 1) && (${new_year} == 1)) { # Special case for Jan 1 (only!) $logtime = timelocal(${sec}, ${min}, ${hour}, ${mday}, ${mon}, ${current_year}); } $logtime -= ((int($offsets{${host}}) / 100) * 100) if (defined($offsets{${host}})); if (${logtime} < ${then}) { $reason = "too old"; return 0; } # Reset host if line is from the future; we may have messages from o +ne # year ago. Note that this may make the global counts wrong. if (${logtime} > (time + 3600)) { # Allow 1 hour slack time $last{${host}} = ""; $counts{${host}} = 0; delete($messages{${host}}); undef($times{${host}}{oldest}); undef($times{${host}}{newest}); $reason = "future"; return -1; } my %month_to_mon = # Convert month names to numbers (Jan +== 0) ( "Jan" => 0, "Feb" => 1, "Mar" => 2, "Apr" => 3, "May" => 4, "Jun" => 5, "Jul" => 6, "Aug" => 7, "Sep" => 8, "Oct" => 9, "Nov" => 10, "Dec" => 11, "JAN" => 0, "FEB" => 1, "MAR" => 2, "APR" => 3, "MAY" => 4, "JUN" => 5, "JUL" => 6, "AUG" => 7, "SEP" => 8, "OCT" => 9, "NOV" => 10, "DEC" => 11); my @mon_to_month = # Convert month numbers to names (0 == + Jan) qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);

        I wrote:

        "Please provide a short script that reproduces your problem. How to do this is explained in "How do I post a question effectively?"."

        Had you followed that link, you would have seen, in big, friendly letters, at the top of the screen:

        Paste actual code that reproduces the problem ...

        You haven't posted actual code. You haven't posted code that reproduces your problem.

        In addition, hundreds of lines of code is not a "short script": a dozen or so lines normally suffices for this sort of task. If you ever do need to post large tracts of code, data, etc., wrap it in <spoiler>...</spoiler> or <readmore>...</readmore> tags: see "Markup in the Monastery" and "Writeup Formatting Tips".

        What you have posted appears to have been copied from your browser. It looks like the code you posted a couple of hours before in this thread; it includes all the '+' signs at the beginning of lines (that indicate that a line wrapped) which are not part of the Perl code.

        If you can post a short script that shows Time::Local::timelocal() giving your "Timelocal out of range message", I'll be happy to look at it further.

        In writing a short script to reproduce your problem, you'll often gain insights into why your original code was having issues.

        Something else you can do is to add print statements to your original code to see what actual values are being passed to your functions. This can often point to where the problem lies.

        -- Ken

Re: Timelocal out of range message
by Corion (Patriarch) on Jan 29, 2014 at 07:27 UTC

    Most likely you are passing in the wrong value for the month. Most likely, that value is 1, and you intend it to be for January, but 1 is for February in the Time::Local API.

Re: Timelocal out of range message
by hdb (Monsignor) on Jan 29, 2014 at 07:23 UTC

    Read the source, Luke.

    The error message smells like you are trying to refer to a 29th of February in a non-leap year. Do you not get a reference to a line number in Local.pm? Check what happens there! Probably line 116?

      Here is the local.pm code snippet that says about out of range errors. Not sure how it's coming for day 29th?

      croak "Month '$month' out of range 0..11" if $month > 11 || $month +< 0; croak "Day '$_[3]' out of range 1..31" if $_[3] > 31 || $_[3] + < 1; croak "Hour '$_[2]' out of range 0..23" if $_[2] > 23 || $_[2] + < 0; croak "Minute '$_[1]' out of range 0..59" if $_[1] > 59 || $_[1] + < 0; croak "Second '$_[0]' out of range 0..59" if $_[0] > 59 || $_[0] + < 0;

        In the version I have it looks like:

        my @MonthDays = ( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ); ... my $md = $MonthDays[$month]; ++$md if $month == 1 && _is_leap_year( $year + 1900 ); croak "Day '$mday' out of range 1..$md" if $mday > $md or $md +ay < 1; croak "Hour '$hour' out of range 0..23" if $hour > 23 or $ho +ur < 0; croak "Minute '$min' out of range 0..59" if $min > 59 or $mi +n < 0; croak "Second '$sec' out of range 0..59" if $sec >= 60 or $se +c < 0;
Re: Timelocal out of range message
by vinoth.ree (Monsignor) on Jan 29, 2014 at 07:40 UTC
    Corion++

    You will see that Months are set in an array:

    my @MonthDays = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);

    Therefore, when it tries to pull the number of days for the month you passed to it, it's searching it's index from 0 to 11. So when you pass in your Month as 1 (Feb), it's searching the array and thinking only 28 days are avilable and failing when you specify 29 since you are technically 'out of range'.


    All is well
      my @MonthDays = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);

      So does that mean 29 is being passed as the day of the month for Feb? I see nowhere in the script that the day of the month is passed to timelocal. Is there any way we can control this for Feb (along with a leap year)?

Re: Timelocal out of range message
by Laurent_R (Canon) on Jan 29, 2014 at 07:46 UTC
    The error probably occurs at line 732 of your code. Please check its syntax.

      Thanks all for your replies! Let me just explain what this script does. It pulls the systems message file from a list of hosts and put the content in a single file output on a central server from where it's run as cron job. I'm pasting a portion of code that's responsible for timelocal() execution in the script. The code I'm pasting contains three subroutines: addMessage(), processFile(), getLogsSCP(). getLogsSCP() will scp the files to central server. processFile() is called within getLogsSCP() and processFile() furthur calls addMessage(). Finally it's the addMessage() that invokes timelocal(). I've also pasted some used for computing the parameters passed to the timelocal function. This line of the code is generating the error.

      my $logtime = timelocal(${sec}, ${min}, ${hour}, ${mday}, ${mon}, ${current_year} - ${new_yea +r});

      Please see the code below.

      sub getLogsSCP { my @hosts = @_; dprint "Getting logs for the last ${cutoff} hours"; dprint " from " . @hosts . " hosts\n"; # Build command to list log files # Using find allows us to scan only files that are new enough my $ssh_command = "find"; $ssh_command .= " @messages_files"; $ssh_command .= " -mtime -" . (int(${cutoff} / 24) + 1); my $nHosts = 1; foreach (@hosts) { chomp; my $host = $_; $current_host = ${host}; $last{${host}} = ""; my $c = 0; while (<READER>) { chomp; my $file = $_; my $tmpfile = "/tmp/logmon.${host}.$hnow{year}$hnow{mon}$hnow{md +ay}.$ENV{LOGNAME}"; # Copy log file dprint localtime() . " Copying ${file} to ${tmpfile}\n"; my $scp = Net::SCP->new({"host" => ${host}}); $scp->get(${file}, ${tmpfile}); # Process log file my $size = (stat("${tmpfile}"))[7]; if (! defined(${size})) { dprint localtime() . " Failed to copy ${file} from ${host}\n"; addFakeMessage(${host}, "FAILED TO COPY ${file} FROM ${host}") +; $total_count++; next; } dprint localtime() . " Parsing ${tmpfile} (${size} bytes)\n"; open(FD, "${tmpfile}") or next; unlink "${tmpfile}"; $c += processFile(\*FD, ${tmpfile}, ${host}); close(FD); } push @emptyhosts, ${host} if ($c == 0); $total_count += $c; close(*READER); close(*WRITER); close(*ERR); } dprint "\n"; } # Process a log file (or stream) sub processFile() { my $FD = shift; # Caller must open and close file descrip +tor my $file = shift; my $host = shift; my $c = 0; my $line = 1; while (<FD>) { # Log file is too big if ($line++ > ${max_messages}) { dprint "\n" . localtime() . " Too many messages in ${file}"; addFakeMessage(${host}, "STOPPED PROCESSING ${file} AFTER ${max_ +messages} MESSAAGES (please rotate)"); $total_count++; last; } dprint "${line}..." if (($line % 10000) == 0); my $retval += addMessage($_); if (${retval} < 0) { $c = 0; } else { $c += ${retval}; } } dprint "\n" if (${line} > 10000); return ${c} ; } sub addMessage($) { my $msg = shift; chomp ${msg}; # Skip blank lines if (${msg} =~ /^$/) { $reason = "blank"; return 0; } # Parse message my ($month, $mday, $tm, $host, @message) = split(/ ?/, ${msg}); my ($hour, $min, $sec) = split(/:/, ${tm}) if (defined(${tm})); # Skip line if we don't know the hostname if (! defined(${host}) || ${host} eq "") { $reason = "hostname" ; re +turn 0; } # Skip lines that don't look like syslog messages unless (${tm} =~ /[0-2]\d:[0-5]\d/) { $reason = "tm = ${tm}"; return + 0; } # Skip lines that are too old my $mon = $month_to_mon{${month}}; my $logtime = timelocal(${sec}, ${min}, ${hour}, ${mday}, ${mon}, ${current_year} - ${new_yea +r}); <----------------- This line is producing the error. if ((${mon} == 0) && (${mday} == 1) && (${new_year} == 1)) { # Special case for Jan 1 (only!) $logtime = timelocal(${sec}, ${min}, ${hour}, ${mday}, ${mon}, ${current_year}); } $logtime -= ((int($offsets{${host}}) / 100) * 100) if (defined($offsets{${host}})); if (${logtime} < ${then}) { $reason = "too old"; return 0; } # Reset host if line is from the future; we may have messages from o +ne # year ago. Note that this may make the global counts wrong. if (${logtime} > (time + 3600)) { # Allow 1 hour slack time $last{${host}} = ""; $counts{${host}} = 0; delete($messages{${host}}); undef($times{${host}}{oldest}); undef($times{${host}}{newest}); $reason = "future"; return -1; } my %month_to_mon = # Convert month names to numbers (Jan +== 0) ( "Jan" => 0, "Feb" => 1, "Mar" => 2, "Apr" => 3, "May" => 4, "Jun" => 5, "Jul" => 6, "Aug" => 7, "Sep" => 8, "Oct" => 9, "Nov" => 10, "Dec" => 11, "JAN" => 0, "FEB" => 1, "MAR" => 2, "APR" => 3, "MAY" => 4, "JUN" => 5, "JUL" => 6, "AUG" => 7, "SEP" => 8, "OCT" => 9, "NOV" => 10, "DEC" => 11); my @mon_to_month = # Convert month numbers to names (0 == + Jan) qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);

      Sample output

      --------------------host1---------------- Feb 2 22:58 host1 SAPBJD_03[17692]: [ID 503851 user.error] Unable to +open trace file sapstartsrv.log. (Error 11 Resource temporarily unava +ilable) [ntservsserver.cpp 3231] Feb 2 22:58 host1 SAPBJD_02[19005]: + [ID 503851 user.error] Unable to open trace file sapstartsrv.log. (E +rror 11 Resource temporarily unavailable) [ntservsserver.cpp 3231] Fe +b 2 23:00 burton SAPBWD_00[22453]: [ID 365072 user.error] Unable to +open trace file sapstartsrv.log. (Error 11 Resource temporarily unava +ilable) [ntservsserver.cpp 3405] --------------------host2---------------- --------------------host3---------------- Permission denied, please try again. Permission denied, please try again. Permission denied (publickey,password,keyboard-interactive). --------------------host4---------------- --------------------host5---------------- ssh: connect to host host5 port 22: Connection timed out --------------------host6---------------- Feb 2 04:08 host6 automountd[29387]: [ID 834250 daemon.error]