Beefy Boxes and Bandwidth Generously Provided by pair Networks
Welcome to the Monastery
 
PerlMonks  

DateTime::Span intersection inconsistencies

by mikeman (Acolyte)
on Jun 30, 2011 at 12:39 UTC ( [id://912176]=perlquestion: print w/replies, xml ) Need Help??

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

When using DateTime::Span->intersects to check for intersections, the spans produced by DateTime::Span->from_datetime_and_duration, and those produced by DateTime::Span->from_datetimes, give what appear to be inconsistent results:

use strict; use warnings; use DateTime; use DateTime::Span; use Test::More 'no_plan'; my $dt1 = DateTime->new( day => 1, month => 1, year => 2011, hour => 12, minute => 0, second => 0, ); my $dt2 = $dt1->clone->add( hours => 1 ); my $duration = DateTime::Duration->new( hours => 1 ); { my $span = DateTime::Span->from_datetimes( start => $dt1, end => $dt2, ); diag $span->start->datetime(); diag $span->end->datetime(); is( $span->intersects( $dt1 ), 1, 'Spanset intersects $dt1' ); is( $span->intersects( $dt2 ), 1, 'Span intersects $dt2' ); is( DateTime::Duration->compare( $span->duration, $duration ), 0, 'Duration is 1 hour' ); } { my $span = DateTime::Span->from_datetime_and_duration( start => $dt1, duration => $duration, ); diag $span->start->datetime(); diag $span->end->datetime(); is( $span->intersects( $dt1 ), 1, 'Span intersects $dt1' ); is( $span->intersects( $dt2 ), 1, 'Span intersects $dt2' ); is( DateTime::Duration->compare( $span->duration, $duration ), 0, 'Duration is 1 hour' ); }

Here are the test results:

# 2011-01-01T12:00:00 # 2011-01-01T13:00:00 ok 1 - Spanset intersects $dt1 ok 2 - Span intersects $dt2 ok 3 - Duration is 1 hour # 2011-01-01T12:00:00 # 2011-01-01T13:00:00 ok 4 - Span intersects $dt1 not ok 5 - Span intersects $dt2 # Failed test 'Span intersects $dt2' # at /home/mike/test.t line 51. # got: '0' # expected: '1' ok 6 - Duration is 1 hour 1..6 # Looks like you failed 1 test of 6.

Note: subtracting one nanosecond from $dt2 in the failing test causes it to succeed:

is( $span->intersects( $dt2->clone->subtract( nanoseconds => 1 ) ), 1, 'Span intersects $dt2' );

It therefore looks as though one nanosecond is going missing somewhere.

Is this intentional -- am I missing something to do with DateTime::Duration objects, or is this down to a bug in one of the DateTime modules?

I'm using DateTime version 0.70 and DateTime::Span version 0.30 (the latest versions at the time of writing).

Update: After quite a lot of digging, I discovered that the new end of set created by DateTime::Span->from_datetime_and_duration is open by default -- it is a semi-open set with a start and open end. The end is therefore before the time+duration (by one nanosecond). That explains why the intersection is false.

The fact that the end of the set is open is stated in the docs, but I had not understood its ramifications. I'm still not clear on why the end of the set defaults to being open, but at least I can work around the issue I had.

Replies are listed 'Best First'.
Re: DateTime::Span intersection inconsistencies
by Anonymous Monk on Jun 30, 2011 at 13:12 UTC

    It therefore looks as though one nanosecond is going missing somewhere.

    Is this intentional -- am I missing something to do with DateTime::Duration objects, or is this down to a bug in one of the DateTime modules?

    I inserted this near the failing test

    use DDS; diag Dump( $span->duration, $duration );
    and it produced
    # $DateTime_Duration1 = { # days => 0, # end_of_month # => 'wrap', # minutes => 0, # months => 0, # nanoseconds # => 0, # seconds => 3600 # }; # $DateTime_Duration2 = { # days => 0, # end_of_month # => 'wrap', # minutes => 60, # months => 0, # nanoseconds # => 0, # seconds => 0 # }; # bless( $DateTime_Duration1, 'DateTime::Duration' ); # bless( $DateTime_Duration2, 'DateTime::Duration' );
    So it apperas that DateTime::Duration->compare is not normalizing the units so the comparison fails. I consider this a bug.

      The problem is that DateTime::Span->intersects is producing different results depending on the method used to create the spans. I included the comparison to show that DateTime::Duration->compare considers both durations to be the same, not different.

        Oh wow, I am blind :)

        I would send bug upstream ASAP :)

Re: DateTime::Span intersection inconsistencies
by ikegami (Patriarch) on Jun 30, 2011 at 16:54 UTC
    If a one-hour event starts at 12:00, the event is over by 13:00. The test is wrong.

      Which test? The first test where there is an intersection, or the second test where this is no intersection? Note that the end time and duration of each span is the same.

      As I wrote in my update, I now understand why I get those results, but they are still inconsistent. An event that ends at 13:00 either intersects with an event that starts at 13:00, or it doesn't -- surely it can't do both.

        Which test?

        I only investigated the failing test, #5.

        Note that the end time and duration of each span is the same.

        That could indicate a problem with test #2 or the possibility for confusion in reading the code for test #2.

        There's no problem with test #2 because the docs clearly show one should use before instead of end if you want test #2 to be consistent with test #5.


        Fixed test:

        use strict; use warnings; use DateTime; use DateTime::Span; use Test::More tests => 6; my $dt1 = DateTime->new( day => 1, month => 1, year => 2011, hour => 12, minute => 0, second => 0, ); my $dt2 = $dt1->clone->add( hours => 1 ); my $duration = DateTime::Duration->new( hours => 1 ); { my $span = DateTime::Span->from_datetimes( start => $dt1, before => $dt2, ); diag $span->start->datetime(); diag $span->end->datetime(); ok( $span->intersects( $dt1 ), 'Span intersects $dt1' ); ok( !$span->intersects( $dt2 ), 'Span doesn\'t intersects $dt2' ); is( DateTime::Duration->compare( $span->duration, $duration ), 0, 'Duration is 1 hour' ); } { my $span = DateTime::Span->from_datetime_and_duration( start => $dt1, duration => $duration, ); diag $span->start->datetime(); diag $span->end->datetime(); ok( $span->intersects( $dt1 ), 'Span intersects $dt1' ); ok( !$span->intersects( $dt2 ), 'Span doesn\'t intersects $dt2' ); is( DateTime::Duration->compare( $span->duration, $duration ), 0, 'Duration is 1 hour' ); } 1;
        1..6 # 2011-01-01T12:00:00 # 2011-01-01T13:00:00 ok 1 - Span intersects $dt1 ok 2 - Span doesn't intersects $dt2 ok 3 - Duration is 1 hour # 2011-01-01T12:00:00 # 2011-01-01T13:00:00 ok 4 - Span intersects $dt1 ok 5 - Span doesn't intersects $dt2 ok 6 - Duration is 1 hour

        PS - I used ok instead of is because intersects is documented to return a boolean, not zero or one.

Re: DateTime::Span intersection inconsistencies
by jpl (Monk) on Jun 30, 2011 at 18:29 UTC
    I'm still not clear on why the end of the set defaults to being open

    If you want to break up an arbitrary domain into mutually exclusive, collectively exhaustive ranges, then half-open intervals are often easier to deal with (and reason about) than closed intervals. Consider real numbers, or floating point approximations thereto.

    Update: Or, for that matter, time intervals. The DateTime interface implies you can specify nanosecond precision, but it doesn't preclude greater precision. If you restrict intervals to be closed, you cannot deal with times that fall between the end of one interval, and the start of the "next". With half-open intervals, there need not be a notion of "next", just a notion of "this is an acceptable place to start and/or end an interval".

      The thirteenth hour of the day starts at 12:00 pm and lasts 60 minutes. Is 1:00 pm part of the thirteenth hour? No.

      According to you, the module would answer "yes" if it was easier to do so. Ease has nothing to do it. It's simply behaving correctly.

        The thirteenth hour of the day starts at 12:00 pm and lasts 60 minutes. Is 1:00 pm part of the thirteenth hour? No.

        According to you, the module would answer "yes" if it was easier to do so. Ease has nothing to do it. It's simply behaving correctly.

        I was addressing the OP's question about why it might make sense to default time intervals to half-open, rather than closed intervals. Your comment tells me that you define a day as a half-open interval, and you choose to make midnight part of the "following day", not part of the "previous day", a decision I would agree with. In my corner of the world, anyway, we wouldn't get universal agreement on that decision, a proof, I suppose, that if you leave a definition (or interface) ill-defined, confusion will ensue.

        Where ease comes in is making an interface easy to use correctly, which can be tremendously difficult. From the discussion here, it is quite clear that the interface is not easy to reason about. That would not be fixed by making intervals default to being closed, nor would it be fixed (IMHO) by adding an option to paper over possible confusion by adding "fuzz".

Re: DateTime::Span intersection inconsistencies
by sundialsvc4 (Abbot) on Jul 01, 2011 at 13:10 UTC

    If anyone’s going to open a bug-ticket on this one, I would like to see it provided with a “fuzz factor.”   This would serve to eliminate the “one nanosecond problem,” among other things.

    What this logic would do, is to add the “fuzz factor” to the upper-end or the lower-end (as the case may be) of two intervals that are going to be compared, only of course for the purposes of comparison.   (If you set a fuzz-interval of two seconds, for example, then an interval ending at "09:59" would be treated as though it ended at "10:01" for the purpose of comparison with an interval that starts at "10:00" and is therefore treated as though it started at "09:59.")   Even though computer clocks in a network are routinely synchronized to one another, they can still be off by a couple of jiffies.   A numerical “less-than” comparison is sometimes too ruthlessly precise.   Whether the fuzz-factor is set large or small, this improvement would make the module considerably more useful.

      This would serve to eliminate the “one nanosecond problem,” among other things.

      Adding a fuzz factor would not fix passing the wrong arguments to the constructor (as was the case here), it would just hide the error.

      As for doing a fuzzy comparison, it's already quite easy.

      I certainly won't be opening a bug ticket, as the problem was not due to a bug. I started with some reasonably complex code which produced results that I was not expecting. I then boiled that code down to a much smaller test script, and along the way I failed to correctly read and understand the associated documentation.

      It was only after posting the question that I realised my mistake - at that point the reason for the seemingly inconsistent results became clear, and the results made perfect sense.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://912176]
Approved by moritz
Front-paged by toolic
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others scrutinizing the Monastery: (6)
As of 2024-04-20 02:30 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found