Beefy Boxes and Bandwidth Generously Provided by pair Networks DiBona
Perl Monk, Perl Meditation
 
PerlMonks  

Unexplained Behavior in Date::Calc

by jpfarmer (Pilgrim)
on Dec 01, 2003 at 16:19 UTC ( [id://311401]=perlquestion: print w/replies, xml ) Need Help??

This is an archived low-energy page for bots and other anonmyous visitors. Please sign up if you are a human and want to interact.

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

I think I've uncoveded a bug in Date::Calc, but before I submit a bug report, I'd like some feedback.

Using the sample script below, I recieve the following output:

Today_and_Now value: 2003-12-1-14-52-15
End time value: 2004-4-1-0-0-0

Function     |Yrs  |Mons |Hrs  |Days |Mins |Secs
-------------------------------------------------
Delta_DHMS   |  N/A|  N/A|  121|    9|    7|   45
Delta_YMDHMS |    1|   -8|    0|  -14|  -52|  -15

Notice how Delta_DHMS calculates each value, while Delta_YMDHMS shows offset from the years of difference. I can't tell if this is by design or not according to the module docs. The nearest thing I can find to an explination is this passage:

This function is based on the function "Delta_YMD()" above but additionally calculates the time difference. When a carry over from the time difference occurs, the value of "$D_d" is adjusted accordingly, thus giving the correct total date/time difference.

The Date::Calc Readme doesn't make any mention of changes regarding this function, so I don't think it's something that's been changed recently.

I guess the bottom line is, I'm not confident enough in my debugging skills to know if I've discovered a bug, am misreading the documentation, or if something is goofy on the system I'm working with. Any feedback would be greatly appreciated.

#!/usr/bin/perl use warnings; use strict; use Date::Calc qw/Time_to_Date Delta_YMDHMS Delta_DHMS Today_and_Now/; my ( $year, $month, $day, $hour, $min, $sec ) = Today_and_Now(); my ( $end_year, $end_month, $end_day, $end_hour, $end_min, $end_sec ) = ( 2004, 4, 1, 0, 0, 0 ); my @cached_today_and_now = Today_and_Now; print "Today_and_Now value: ", join('-', @cached_today_and_now), "\n"; print "End time value: 2004-4-1-0-0-0\n\n"; print 'Function |', join ( '|', map { sprintf( "%-5s", $_ ) } qw/Yrs Mons Hrs Days Mins Secs/ ), "\n"; print "-" x 49, "\n"; # The two N/As are added in to pad the results of Delta_DHMS, as it # returns two fewer values and I wanted the table to line up neatly print 'Delta_DHMS |', join ('|', (' N/A', ' N/A', map { sprintf( "%5d", $_ ) } Delta_DHMS( @cached_today_and_now, 2004, 4, 1, 0, 0, 0 ))), "\n"; print 'Delta_YMDHMS |', join ( '|', map { sprintf( "%5d", $_ ) } Delta_YMDHMS( @cached_today_and_now, 2004, 4, 1, 0, 0, 0 ) ), "\n";

Replies are listed 'Best First'.
Re: Unexplained Behavior in Date::Calc
by mojotoad (Monsignor) on Dec 01, 2003 at 16:37 UTC
    The module is working as advertised. There's this concept out there of performing mathematical operations on non-anchored date fragments which is not really that useful in the real world, presuming you want accurate answers.

    When dealing with absolute dates (meaning YMDHMS, rather than non-anchored date fragments) you are better off converting both dates into seconds-since-epoch, computing the delta, then applying this delta (in seconds) to any target dates of interest. (all math happens in the world of seconds).

    This is still fraught with potential errors, however, including DST issues and leap-seconds.

    Check out the collection of nodes in the Q&A section QandASection: dates and times.

    I'd also suggest looking into the Perl DateTime Suite if you're interested in detailed, and correct, date calculations.

    Matt

      I've just got to give you a ++ for the pointer to Perl DateTime Suite,I just browsed it, now I'm gonna try it.

      Also when stating that the module is working as advertised, Delta_DHMS() does, right in its name, tell you that it DOESN'T know or care about years; else it would have been named Delta_YDHMS(). That said all the functions should use absolute dates. (It's a feature;) .)


      _________________

      madams@scc.net

      messaging note:
      s/(Subject:.*)/$1 <pass:SIGUSR2003>/

        Both Delta_DHMS() and Delta_YMDHMS() correspond to their companion functions Add_Delta_DHMS() and Add_Delta_YDHMS(). In both cases, the results from Delta_* can be passed to the Add_Delta_* function, along with the first date, in order to get the second date. Accordingly, the complimentary operation is supported if you reverse the sign of each value of the delta and pass to Add_Delta_* along with the second date.

        From the POD:

        In any case, adding the result of this function to the first date/time + value ($year1,$month1,$day1, $hour1,$min1,$sec1) always gives the se +cond date/time value ($year2,$month2,$day2, $hour2,$min2,$sec2) again +, and adding the negative result (all elements of the result vector n +egated) to the second date/time value gives the first date/time value +.

        As you can see, the specification for what Delta_YMDHMS() and Delta_DHMS() return is defined in terms of complimentary operations, not any particular representation of values. So in your example (hr/day labels fixed):

        Today_and_Now value: 2003-12-1-14-52-15
             End time value: 2004-4-1-0-0-0
        
        Function     |Yrs  |Mons |Days |Hrs  |Mins |Secs
        -------------------------------------------------
        Delta_DHMS   |  N/A|  N/A|  121|    9|    7|   45
        Delta_YMDHMS |    1|   -8|    0|  -14|  -52|  -15
        
        Add_Delta_YMDHMS() (no modulo, no carry):
        
          original: 2003 |  12 |   1 |  14 |  52 |  15
        +    delta:    1 |  -8 |   0 | -14 | -52 | -15
        -------------------------------------------------
               end: 2004 |   4 |   1 |   0 |   0 |   0
        -    delta:    1 |  -8 |   0 | -14 | -52 | -15
        -------------------------------------------------
          original: 2003 |  12 |   1 |  14 |  52 |  15
        
        Add_Delta_DHMS() (modulo, columnar carry, calendrical):
        
              math:|   calendrical   |mod24|mod60|mod60
        -------------------------------------------------
          original:       2003-12-01 |  14 |  52 |  15
        +    delta:              121 |   9 |   7 |  45
        -------------------------------------------------
               end:       2004-04-01 |   0 |   0 |   0
        -    delta:              121 |   9 |   7 |  45
        -------------------------------------------------
          original:       2003-12-01 |  14 |  52 |  15
        
        
        As you can see, there's no actual calendrical or modulo/carrying calculations going on with Delta_YMDHMS(). Delta_DHMS(), on the other hand, does do it that way.

        Nowhere in the documentation does it state that the Delta_* functions perform their arithmetic in the same fashion, or represent their results the same way. They are merely guaranteed to provide symmetry for the Add_Delta_* functions.

        Matt

Re: Unexplained Behavior in Date::Calc
by pg (Canon) on Dec 01, 2003 at 22:46 UTC
    "Function |Yrs |Mons |Hrs |Days |Mins |Secs"

    I think you misplaced "Hrs" and "Days", otherwise the numbers showed underneath are really buggy, and you should really fire a bug report ;-)

    BTW, I don't think what you mentioned is a bug, instead it just shows that you have a different requirement other than what the function delivers.

Re: Unexplained Behavior in Date::Calc (yuck)
by tye (Sage) on Dec 02, 2003 at 02:55 UTC

    I was waiting to see what other reponses turned up. But I strongly disagree that the results shown for Delta_YMDHMS are reasonable. This is like subtracting 1.483289 - 2.12314 and Perl returning to you "-1 + 0.3 + 0.06 + 0.0001 + 0.00004 + 0.000009". Gee, if I wanted to just pair-wise subtract at each position, I'd do that myself.

    "How much time passed between these two date-times?" "Exactly 1 year less 11 months less 28 days less 22 hours less 58 minutes plus 17 seconds."

    A "difference" of dates should consist of either all positive values or all negative values.

    What's next? Should it be okay for a "date" module to return "19103/12/32" for the first of next year because the documentation doesn't state that it won't?

    <Update> Unfortunately, my solution is incorrect. The Delta_YMDHMS is wonkier than I thought. The paragraph I quote below could explain the problem if the result was -1 year plus 8 months plus 14 hours etc. but the result was actually 1 year minus 8 months etc. So the dates were given in chronological order and Delta_YMDHMS is just wonky.

    But much of Date::Calc is at least a bit wonky. When I say that I'll probably never use it, I don't mean to imply that the module is not useful. It has been heavilly used and so appears to get complex date calculations correct (even if the interface isn't one I'd praise). It was more out of surprise after having heard it recommended so many times.

    It also reflects that there are other modules to choose from and much of what Date::Calc does I have no problem rolling my own solutions for. I use localtime and Time::Local -- even though the latter appears to me to have missed part of the point of C defining mktime(), that you can do date arithmatic with it because it wraps overflow and underflow ("If structure members are outside their legal interval, they will be normalized").

    Yes, Date::Calc isn't limited to the "Unix epoch", but I have yet to need date calculations outside of that...

    Oh, and the Compare_Vectors() function below would still be a useful addition to Date::Calc. (: </Update>


    Checking the documentation myself I see that you cut off quoting it just as it is about to say:

    Arguments are expected to be in chronological order to yield a (usually) positive result.
    which explains the problem.

    If the algorithm works wonky when the dates are in the wrong order, then that is easy to fix. For this simple fix, I need the quick-and-easy way to compare to two date-times so I checked the Date::Calc docs and was horrified. So I'll also add a sane "compare" function for date-times. Here is what Date::Calc wants you to write every time you want to compare two date-times:

    use Date::Calc qw( Add_Delta_DHMS Date_to_Days ); @date1 = (2002,8,31,23,59,1); @date2 = (2002,9,1,11,30,59); @d1 = ( Date_to_Days(@date1[0..2]), ($date1[3]*60+$date1[4])*60+$dat +e1[5] ); @d2 = ( Date_to_Days(@date2[0..2]), ($date2[3]*60+$date2[4])*60+$dat +e2[5] ); @diff = ( $d1[0]-$d2[0], $d1[1]-$d2[1] ); if ($diff[0] > 0 and $diff[1] < 0) { $diff[0]--; $diff[1] += 86400; } if ($diff[0] < 0 and $diff[1] > 0) { $diff[0]++; $diff[1] -= 86400; } if ($diff[0] > 0 || $diff[0] == 0 && $diff[1] >= 0) { ... }
    My word!! No wonder they haven't fixed Delta_YMDHMS!

    Here is a fixed Delta_YMDHMS which is how Date::Calc should be patched to work for the sake of sanity (untested):

    sub Compare_Vectors { my( $x, $y )= @_; for my $i ( 0..$#x ) { my $cmp= $x[$i] <=> ( $y[$i] || 0 ); return $cmp if $cmp; } return 0 <=> ( $y[@x] || 0 ); } sub Delta_YMDHMS { my $x= [ splice @_, 0, 6 ]; my $y= [ @_ ]; my $sign= 1; if( Compare_Vectors( $x, $y ) < 0 ) { $sign= -1; ( $x, $y )= ( $y, $x ); } return map $sign*$_, Date::Calc::Delta_YMDHMS( @$x, @$y ); }

    But the main thing I learned from this exercise is that I will probably never use Date::Calc. Even with this brief exposure to it, I find the module very scary.

                    - tye
      I'm going to (quietly and without strong emphasis) defend Date::Calc. I've used it on many occasions and found it to be very helpful. But, then again, I never did anything wonky with it. Mostly, it was for date offsets and I wrote my own object around it. *shrugs* Having it on hand was nice cause DateTime didn't exist yet. Now that it does, I'll probably switch. But, for the nonce, it is still on my list of modules to install whenever I build a new dev server.

      ------
      We are the carpenters and bricklayers of the Information Age.

      Please remember that I'm crufty and crochety. All opinions are purely mine and all code is untested, unless otherwise specified.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://311401]
Approved by Itatsumaki
Front-paged by jonnyfolk
help
Sections?
Information?
Find Nodes?
Leftovers?
    Notices?
    hippoepoptai's answer Re: how do I set a cookie and redirect was blessed by hippo!
    erzuuliAnonymous Monks are no longer allowed to use Super Search, due to an excessive use of this resource by robots.