2
votes

I have a function I'm trying to re-create using the DateTime module in Perl:

$x=dates_difference($year,$month,$day,$cyear,$curmon,$cmday)

given two dates, the current date/time in the format of: $cyear, $cmon, $cmday (year, month number, day of month number) and another date that will be earlier in the same format: $year, $month, $day, I want to return the number of days between the two dates. I don't need it to be 100% precise, as long as it's +/- a day or two so time and timezones don't really matter.

I've been reading through various examples and can't find anything very clear.

I tried this:

$x = $dtcurrent->subtract_datetime_absolute($dt1);

and got an error DateTime::Duration does not overload comparison. See the documentation on the compare() method for details.

Example:

$x = $dtcurrent->delta_days($dt1);
if ($x > 30) ...

it does not appear to be returning a number I can use in a numeric comparison.

4
What have you tried so far? What examples are you looking at and in what way do you judge them unclear? - Paul L
One sec.. let me get this format - Arkan
I tried this: $x=$dtcurrent->subtract_datetime_absolute($dt1); and got an error DateTime::Duration does not overload comparison. See the documentation on the compare() method for details. - Arkan
Please add this to your question, and show input for which you get errors. - zdim
This now makes far more sense. Please update the question with all this information -- what you tried and what doesn't work (and how) or you don't know how to do. That's precisely what should be in the question. - zdim

4 Answers

1
votes

You want to use delta_days.

sub dates_difference {
   my ($year1, $month1, $day2, $year2, $mon2, $day2) = @_;
   my $dt1 = DateTime->new( year => $year1, month => $month1, day => $day1, time_zone => 'floating' );
   my $dt2 = DateTime->new( year => $year2, month => $month2, day => $day2, time_zone => 'floating' );
   return $dt2->delta_days($dt1)->in_units('days');
}

The following is more efficient:

sub dates_difference {
   my ($year1, $month1, $day2, $year2, $mon2, $day2) = @_;
   my $dt1 = DateTime->new( year => $year1, month => $month1, day => $day1, time_zone => 'floating' );
   my $dt2 = DateTime->new( year => $year2, month => $month2, day => $day2, time_zone => 'floating' );
   return abs( ( $dt2->local_rd_values )[0] - ( $dt1->local_rd_values )[0] );
}

If you want the difference to be signed, use the following:

sub dates_difference {
   my ($year1, $month1, $day2, $year2, $mon2, $day2) = @_;
   my $dt1 = DateTime->new( year => $year1, month => $month1, day => $day1, time_zone => 'floating' );
   my $dt2 = DateTime->new( year => $year2, month => $month2, day => $day2, time_zone => 'floating' );
   return ( $dt2->local_rd_values )[0] - ( $dt1->local_rd_values )[0];
}
1
votes

Use the core Time::Piece module. It's much lighter-weight than DateTime and does all that is necessary

Here's some example code for you

use strict;
use warnings 'all';
use feature 'say';

use Time::Piece ();

my $x = dates_difference(2017, 1, 1, 2016, 5, 9);

say "$x days until the New Year";


sub dates_difference {
    my ($y, $m, $d, $cy, $cm, $cd) = @_;
    my $date  = Time::Piece->strptime("$y $m $d", '%Y %m %d');
    my $cdate = Time::Piece->strptime("$cy $cm $cd", '%Y %m %d');
    ($date - $cdate)->days;
}

output

237 days until the New Year
0
votes

Here's what I came up with that appears to work..

use DateTime;

my $dt_now = DateTime->now( time_zone => 'local' )->set_time_zone('floating');

my $dt1 = DateTime->new(
   year      => $year,
   month     => $month,
   day       => $day,
   time_zone => 'floating',
);

my $days = $dt_now->delta_days($dt1)->in_units('days');
0
votes

Assembling your code lines to an example:

my $dtcurrent = DateTime->new(
    year  => 2016,
    month => 5,
    day   => 9,
);
my $old = DateTime->new(
    year  => 2016,
    month => 1,
    day   => 1,
);

(I'm using a fixed $dtcurrent for easier explanation of the calculation results. Should be $dtcurrent = DateTime->now in production.)

my $distance = $old->delta_days($dtcurrent);

$distance is a DateTime::Duration object which has different properties:

print $distance->years;
0

No surprise: 2016 -> 2016 is 0 years difference.

print $distance->months;
0

print $distance->days;
3

What happend? The DateTime manual says:

The "delta_days()" method returns a duration which contains only days.

Not what you expected. But try ->in_units:

print $distance->in_units('days');
129

Alternate: Date::Calc

I'd prefer Date::Calc for this usage case:

use Date::Calc qw(Delta_Days Today);
print Delta_Days(Today(), 2016, 1, 1);
-129

Swap Today() and the date to get a positive number. Today() may also be replaced by another $year, $month, $day.

Alternate: Timestamps

use Time::Local qw( timegm ); # Core module

my ($year_now, $month_now, $day_now) = ( localtime(time) )[5,4,3];

my $ts     = timegm($year,     $month,     $day,     0, 0, 0);
my $ts_now = timegm($year_now, $month_now, $day_now, 0, 0, 0);

my $days = int(abs(($ts_now - $ts)) / (24*60*60));

timegm does the opposite of gmtime: Convert date parts into a timestamp. Since all gmtime days have 24*60*60 seconds, we can find the number of days between two dates by finding the timestamps of those dates in gmt, calculating the difference, and dividing by 24*60*60. This is obviously very complicated and error prone.