J. Doe
J. Doe

Reputation: 91

Perl - calculate the difference in days between two dates on MS Windows and Linux

My script calculates the difference in days between two dates. However, all the time I encounter errors. The solution must work for all OS. It is advisable to do it in UNIX epoch time, but if it is impossible then there may be another solution.

I tried:
Time::ParseDate - does not work on MS Windows
Time::Local - does not work on dates from the 31st of the month

Sample code:

#!/usr/bin/perl -w
use strict;
use Time::Local;
use POSIX;

sub toepoch {
  my @a = split /[- :]/, $_[0];
  $a[0] =~ s/^.{2}//;
  if (! defined $a[5]) {
    $a[5] = 00
  }
  my $b = timelocal($a[5], $a[4], $a[3], $a[2], $a[1], $a[0]);
  return $b;
}

my $days = sprintf("%d",(&toepoch('2018-03-31 11:00') - &toepoch('2018-04-02 11:00') / 86400));
print $days;

Output: Day '31' out of range 1..30 at epoch.pl line 12.

What module should I check in next? I remind you that the solution must work on UNIX and MS Windows systems.

Upvotes: 0

Views: 556

Answers (1)

Silvar
Silvar

Reputation: 705

From the documentation for Time::Local:

It is worth drawing particular attention to the expected ranges for the values provided. The value for the day of the month is the actual day (ie 1..31), while the month is the number of months since January (0..11). This is consistent with the values returned from "localtime()" and "gmtime()".

So by supplying timelocal the array (0, 00, 11, 31, 03, 18) you're trying to use day 31 of month 4, which doesn't work since April only ever has 30 days. If only the error message included the month it's assuming!

When doing the conversion, you need to mind to keep month values within 0..11 and adjust the year accordingly.

(Alternately you can use timelocal_nocheck() to be allowed to input month -1 and have the function do the conversion to the previous year. Although if you did use that function, you'd have had a bug that was a lot harder to track down, since it would have automatically converted 31st of April to 1st of May and you'd have no idea why your time difference is only 1 day.)

Secondly, you have a misplaced parenthesis on the calculation line, so you divide only the latter time by 86400.

My edited code:

use strict;
use warnings;

use Time::Local;
use POSIX;

sub toepoch {
    my @a = split /[- :]/, $_[0];
    $a[0] =~ s/^.{2}//;
    if (! defined $a[5]) {
        $a[5] = 00
    }
    --$a[1];
    if ($a[1] < 0) {
        --$a[0];
        $a[1] += 12;
    }
    my $b = timelocal($a[5], $a[4], $a[3], $a[2], $a[1], $a[0]);
    return $b;
}

my $days = sprintf("%d",(&toepoch('2018-03-31 11:00') - &toepoch('2018-04-02 11:00')) / 86400);
print $days;

Output:

-2

EDIT:

I assume you know what you're doing when using format %d for the value - it truncates the value down to the next whole number, meaning if you had dates

2018-03-31 11:00
2018-04-02 10:59

that is, just 1 minute short of 2 days, your program would report the time difference as "-1".

To round to nearest whole number, use the format %.0f instead.

Upvotes: 4

Related Questions