http://www.perlmonks.org?node_id=985381

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

I'm trying to calculate the number of years, months, weeks, days, hours, minutes, and seconds which have elapsed since my son's birthday, which was 3/13/12 at 8:16 AM. The script below produces output which is almost correct. One problem is that it does not output the days. Here's the output at the time I ran it:
3 years
4 months
3 weeks
16 hours
16 minutes
13 seconds
The 3 years and 4 months are correct, but 3 weeks does not, and there are no days. What am I doing wrong?

#!/usr/bin/perl use strict; use warnings; use Data::Dumper; use DateTime; $\ = "\n"; my $bday = DateTime->new( year => 2009, month => 3, day => 13, hour => 8, minute => 16 ); my $now = DateTime->now(time_zone => 'America/New_York'); #print Dumper $t2; # subtract_datetime returns a "DateTime::Duration" object my $dtd1 = now$->subtract_datetime($bday); print $dtd1->years . q{ years} if $dtd1->years; print $dtd1->months . q{ months} if $dtd1->months; print $dtd1->weeks . q{ weeks} if $dtd1->weeks; print $dtd1->days . q{ days} if $dtd1->days; print $dtd1->hours . q{ hours} if $dtd1->hours; print $dtd1->minutes . q{ minutes} if $dtd1->minutes; print $dtd1->seconds . q{ seconds} if $dtd1->seconds; print $dtd1->nanoseconds . q{ nanoseconds} if $dtd1->nanoseconds;

Replies are listed 'Best First'.
Re: Problem using DateTime
by Khen1950fx (Canon) on Aug 04, 2012 at 05:29 UTC
    This outputs the days:
    #!/usr/bin/perl -l use strict; use warnings; use Data::Dumper; use DateTime; my $bday = DateTime->new( year => 2009, month => 3, day => 13, hour => 8, minute => 16, second => 30, nanosecond => 0, time_zone => 'America/New_York', ); my $now = DateTime->now; my $dtd1 = $now->subtract_datetime($bday); print $dtd1->years . ' years'; print $dtd1->months . ' months'; print $dtd1->weeks . ' weeks'; print $dtd1->days . ' days'; print $dtd1->hours . ' hours'; print $dtd1->minutes . ' minutes'; print $dtd1->seconds . ' seconds'; print $dtd1->nanoseconds . ' nanoseconds';
    Output:
    3 years 4 months 3 weeks 0 days 18 hours 7 minutes 49 seconds 0 nanoseconds
Re: Problem using DateTime
by tobyink (Canon) on Aug 04, 2012 at 09:13 UTC

    The way we earthlings divide and subdivide our time is inescapably weird. DateTime protects you from some of that weirdness, but as I said, the weirdness is ultimately inescapable.

    Run this:

    use 5.010; use DateTimeX::Auto ':auto'; use DateTime::Format::Human::Duration; sub saydiff { state $fmt = DateTime::Format::Human::Duration::->new; say $fmt->format_duration_between(@_); } saydiff('2009-02-12', '2012-02-12'); saydiff('2009-02-12', '2012-08-12'); saydiff('2009-02-12', '2012-08-11'); saydiff('2009-02-11', '2012-08-11');

    The first two results should be unsurprising:

    3 years
    3 years, 6 months
    

    The next one is somewhat more odd:

    3 years, 5 months, 3 weeks, 6 days
    

    Huh? 3 years, 5 months takes you to the 12th of July; adding 3 weeks to that takes you to the 2nd of August; and then 6 more days is the 8th of August, which is still three days short!

    But then the last result is:

    3 years, 6 months
    

    ... again. And how did we arrive back at that? By shifting one day at the start of the duration. So we see that actually the third result was not wrong; that there are multiple correct answers!, The DateTime module was just selecting a slightly unintuitive one.

    Let's go back to your example. "Now" is 00:32:13 on 4th of August 2012. DateTime claims: 3 years, 4 months, 3 weeks, 16 hours, 16 minutes, 13 seconds.

    So let's backtrack 3 years: 00:32:13 on 4th of August 2009.

    Backtrack 4 months: 00:32:13 on 4th of April 2009.

    Backtrack 3 weeks: 00:32:13 on 14th of March 2009.

    Backtrack 16 hours: 08:32:13 on 13th of March 2009.

    Backtrack 16 minutes: 08:16:13 on 13th of March 2009.

    Backtrack 13 seconds: 08:16:00 on 13th of March 2009.

    ... which is what you wanted!

    So DateTime's result makes perfect sense if you count backwards; just not so much if you count forwards.

    perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'
Re: Problem using DateTime
by Anonymous Monk on Aug 04, 2012 at 06:29 UTC

      Ah, I should have known! Sometimes I wonder if there isn't a perl
      module for turning lead into gold.

      -Thanks

Re: Problem using DateTime
by frozenwithjoy (Priest) on Aug 04, 2012 at 06:34 UTC
    The slides and the video linked in 980626 are really helpful for learning some DateTime tricks and warnings. (as well as learning DateTime usage, in general.)
Re: Problem using DateTime
by Anonymous Monk on Aug 04, 2012 at 05:32 UTC

    Hi,

    Have a look at Date::Calc Delta_YMDHMS. It does this exactly.

    J.C.

Re: Problem using DateTime
by sundialsvc4 (Abbot) on Aug 04, 2012 at 18:04 UTC

    P.S.:   Congratulations, Dad!   Are you getting any sleep yet?   :-)

      After three years, one would certainly hope so. Mine were each sleeping through the night from around 6 months old.

      Besides which, what makes you think zBernie is a dad? Not all Perl programmers are men.

      perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'