Home > Blockchain >  perl's datetime subtraction with sign
perl's datetime subtraction with sign

Time:10-22

I'm trying to use Perl's DateTime to subtract one day from another with a sign. I can get the days in between easily:

sub delta_days {
    my $date1 = shift;
    my $date2 = shift;
    my $d1 = str_to_date_object($date1);
    my $d2 = str_to_date_object($date2);
    return $d2->delta_days($d1)->delta_days();
}
say delta_days('2021-10-21', '1980-8-20');
say delta_days('1980-8-20',  '2021-10-21');

but both of these calls give the difference as 15037, without a sign.

Following the documentation https://metacpan.org/pod/DateTime

I see

# not DST
my $dt1 = DateTime->new(
    year      => 2003,
    month     => 5,
    day       => 6,
    time_zone => 'America/Chicago',
);
 
# is DST
my $dt2 = DateTime->new(
    year      => 2003,
    month     => 11,
    day       => 6,
    time_zone => 'America/Chicago',
);
my $dur = $dt2->subtract_datetime($dt1)->days();

I have checked a similar question in How to make DateTime::Duration output only in days? but I don't see how to get a signed difference there.

How can I get the difference in days with a sign?

CodePudding user response:

Two issues: You're doing the subtraction on dates in the wrong order if you want a negative offset, and with the dates you're testing subtract_datetime() with, the duration object is going to have 0 days.

Compare with this version:

#!/usr/bin/env perl
use strict;
use warnings;
use feature qw/say/;
use DateTime;
use Data::Dumper;

# not DST
my $dt1 = DateTime->new(
    year      => 2003,
    month     => 5,
    day       => 6,
    time_zone => 'America/Chicago',
);

# is DST
my $dt2 = DateTime->new(
    year      => 2003,
    month     => 11,
    day       => 6,
    time_zone => 'America/Chicago',
    );
say $dt1;
say $dt2;

my $dur = $dt1->subtract_datetime($dt2);
print Dumper({$dur->deltas});

which when run produces

2003-05-06T00:00:00
2003-11-06T00:00:00
$VAR1 = {
          'minutes' => 0,
          'nanoseconds' => 0,
          'months' => -6,
          'seconds' => 0,
          'days' => 0
        };

Note the -6 months offset. DateTime::Duration objects will not convert between months and days according to the documentation so this looks like a dead end.

The delta_days method you looked at first returns the actual difference in days, but as noted it's an absolute value. You can add a check to convert it to negative if the first date is before the second:

my $days = $dt1->delta_days($dt2)->in_units('days');
$days *= -1 if $dt1 < $dt2;
say $days; # Prints -184

and a variation of your delta_days() function:

sub delta_days {
    my $date1 = shift;
    my $date2 = shift;
    my $d1 = str_to_date_object($date1);
    my $d2 = str_to_date_object($date2);
    return $d1->delta_days($d2)->in_units('days') * ($d1 < $d2 ? -1 : 1);
}
say delta_days('2021-10-21', '1980-8-20');
say delta_days('1980-8-20',  '2021-10-21');
  • Related