Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Hi Monks,
I want to check if two given dates have a difference of maximum 6 months. I go about like this:
use Date::Calc qw(:all); use strict; my @data_date_confirm=(); my @data_date_birth=(); my $diab_confirm = '29/08/2013'; my $date_birth = '21/02/2013'; if($diab_confirm=~/(\d+)\/(\d+)\/(\d+)/) { my $day_confirm = $1; my $month_confirm = $2; my $year_confirm = $3; @data_date_confirm = ($year_confirm, $month_confirm, $day_conf +irm); } if($date_birth=~/(\d+)\/(\d+)\/(\d+)/) { my $day_birth = $1; my $month_birth = $2; my $year_birth = $3; @data_date_birth = ($year_birth, $month_birth, $day_birth); } my $dd = Delta_Days(@data_date_birth, @data_date_confirm); my $months_until_confirm = sprintf("%.1f", $dd/30); if($months_until_confirm<=6) { print "They are different by more than 6 months!\n"; }

I realise though that this is not entirely correct, since I divide by 30 days. Is it a way to be more precise?
Thank you!

Replies are listed 'Best First'.
Re: More accurate way to calculate Date difference
by haukex (Archbishop) on Aug 11, 2018 at 08:05 UTC

    It's important to be clear on definitions and provide meaningful examples. For example, sometimes a "month" is defined as exactly 30 days, but since you're asking this question, I'm guessing that's not what you want. For example, using DateTime math:

    2016-01-29 + 30 days = 2016-02-28    2017-01-29 + 30 days = 2017-02-28
    2016-01-30 + 30 days = 2016-02-29    2017-01-30 + 30 days = 2017-03-01
    vs.
    2016-01-29 + 1 month = 2016-02-29    2017-01-29 + 1 month = 2017-03-01
    2016-01-30 + 1 month = 2016-03-01    2017-01-30 + 1 month = 2017-03-02
    

    I would recommend this: First, calculate the "deadline" date by adding your "6 months" to the $date_birth. Then, use the methods provided by the library to see if the $diab_confirm is past that date or not. For example, in Date::Calc that would probably be Add_Delta_YM for the first part, and Date_to_Days or Delta_Days for the second.

    Personally, I like DateTime, and its objects overload the comparison operators:

    use warnings; use strict; use DateTime; use DateTime::Format::Strptime; my $date_birth = '21/02/2013'; my $diab_confirm = '29/08/2013'; my $strp = DateTime::Format::Strptime->new(on_error=>'croak', pattern => '%d/%m/%Y'); my $dt_birth = $strp->parse_datetime($date_birth); my $dt_confirm = $strp->parse_datetime($diab_confirm); print " birth: ", $dt_birth->ymd, "\n"; print " confirm: ", $dt_confirm->ymd, "\n"; my $dt_deadline = $dt_birth->clone->add(months=>6); print "deadline: ", $dt_deadline->ymd, "\n"; if ($dt_confirm > $dt_deadline) { print $dt_confirm->ymd, " > ", $dt_deadline->ymd, "\n"; } else { print $dt_confirm->ymd, " <= ", $dt_deadline->ymd, "\n"; } __END__ birth: 2013-02-21 confirm: 2013-08-29 deadline: 2013-08-21 2013-08-29 > 2013-08-21

    As always, the more test cases the better!

Re: More accurate way to calculate Date difference
by hippo (Archbishop) on Aug 11, 2018 at 09:20 UTC

    Room for one more? Here's a generic Time::Piece solution:

    use strict; use warnings; use Time::Piece; use Test::More; my $fmt = '%d/%m/%Y'; my $date = Time::Piece->strptime ('21/02/2013', $fmt); my @within = ('12/08/2013'); my @without = ('29/08/2013'); my $mon = 6; plan tests => @within + @without; for my $other (@within) { ok (within_months ($mon, $date, Time::Piece->strptime ($other, $fm +t)), "$other is within $mon months of $date"); } for my $other (@without) { ok (! within_months ($mon, $date, Time::Piece->strptime ($other, $ +fmt)), "$other is not within $mon months of $date"); } sub within_months { my ($mon, @d) = @_; if ($d[0] > $d[1]) { push @d, shift @d }; my $start = $d[0]->add_months ($mon); return $start > $d[1]; }

    Feel free to expand the @within and @without arrays with more test cases. You can also change $mon to whatever number you require. Time::Piece is in core so there are no extra dependencies needed here.

Re: More accurate way to calculate Date difference
by Athanasius (Archbishop) on Aug 11, 2018 at 07:46 UTC

    Here is one way:

    use strict; use warnings; use DateTime; my $dt1 = DateTime->new ( year => 2013, month => 9, day => 29, ); my $dt2 = DateTime->new ( year => 2013, month => 3, day => 29, ); my $dt3 = DateTime->new ( year => 2013, month => 3, day => 30, ); my $dur1 = $dt2->subtract_datetime($dt1); printf "29/03/2013 to 29/09/2013: %s months\n", $dur1->months; my $dur2 = $dt3->subtract_datetime($dt1); printf "30/03/2013 to 29/09/2013: %s months\n", $dur2->months;

    Output:

    17:44 >perl 1919_SoPW.pl 29/03/2013 to 29/09/2013: 6 months 30/03/2013 to 29/09/2013: 5 months 17:44 >

    Update: Please see the correction by haukex, below.

    Hope that helps,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

      Unfortunately, one has to be careful with DateTime::Duration objects' accessors: ->month only returns the "months" component of the difference. You usually want ->in_units instead, but note the limitations of conversion - for example, you can't convert a number of months to a number of days without knowing which months we're talking about (and that information is no longer available if we only have a duration), so you have to get both the months and days, and in_units will do the conversion from years to months for you.

      use warnings; use strict; use DateTime; my $dt1 = DateTime->new(year => 2013, month => 10, day => 1); my $dt2 = DateTime->new(year => 2018, month => 3, day => 31); my $dur1 = $dt2->subtract_datetime($dt1); print $dt1->ymd, " to ", $dt2->ymd, ": ", $dur1->months, " months - oops!\n"; my ($days,$months) = $dur1->in_units('days','months'); print $dt1->ymd, " to ", $dt2->ymd, ": ", "$months months, $days days", "\n"; __END__ 2013-10-01 to 2018-03-31: 5 months - oops! 2013-10-01 to 2018-03-31: 53 months, 30 days
      That is great!
      Thank you so much!
Re: More accurate way to calculate Date difference
by choroba (Cardinal) on Aug 11, 2018 at 08:03 UTC
    See a recent question here: Date comparison

    ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,