1 # Copyright (c) 2003 Flavio Soibelmann Glock. All rights reserved.
 
   2 # This program is free software; you can redistribute it and/or
 
   3 # modify it under the same terms as Perl itself.
 
   5 package DateTime::Span;
 
  10 use DateTime::SpanSet;
 
  12 use Params::Validate qw( validate SCALAR BOOLEAN OBJECT CODEREF ARRAYREF );
 
  13 use vars qw( $VERSION );
 
  15 use constant INFINITY     => DateTime::INFINITY;
 
  16 use constant NEG_INFINITY => DateTime::NEG_INFINITY;
 
  17 $VERSION = $DateTime::Set::VERSION;
 
  20     my ( $self, $tz ) = @_;
 
  22     $self->{set} = $self->{set}->iterate( 
 
  24             my %tmp = %{ $_[0]->{list}[0] };
 
  25             $tmp{a} = $tmp{a}->clone->set_time_zone( $tz ) if ref $tmp{a};
 
  26             $tmp{b} = $tmp{b}->clone->set_time_zone( $tz ) if ref $tmp{b};
 
  33 # note: the constructor must clone its DateTime parameters, such that
 
  34 # the set elements become immutable
 
  37     my %args = validate( @_,
 
  59     die "No arguments given to DateTime::Span->from_datetimes\n"
 
  62     if ( exists $args{start} && exists $args{after} ) {
 
  63         die "Cannot give both start and after arguments to DateTime::Span->from_datetimes\n";
 
  65     if ( exists $args{end} && exists $args{before} ) {
 
  66         die "Cannot give both end and before arguments to DateTime::Span->from_datetimes\n";
 
  69     my ( $start, $open_start, $end, $open_end );
 
  70     ( $start, $open_start ) = ( NEG_INFINITY,  0 );
 
  71     ( $start, $open_start ) = ( $args{start},  0 ) if exists $args{start};
 
  72     ( $start, $open_start ) = ( $args{after},  1 ) if exists $args{after};
 
  73     ( $end,   $open_end   ) = ( INFINITY,      0 );
 
  74     ( $end,   $open_end   ) = ( $args{end},    0 ) if exists $args{end};
 
  75     ( $end,   $open_end   ) = ( $args{before}, 1 ) if exists $args{before};
 
  77     if ( $start > $end ) {
 
  78         die "Span cannot start after the end in DateTime::Span->from_datetimes\n";
 
  80     $set = Set::Infinite::_recurrence->new( $start, $end );
 
  81     if ( $start != $end ) {
 
  82         # remove start, such that we have ">" instead of ">="
 
  83         $set = $set->complement( $start ) if $open_start;  
 
  84         # remove end, such that we have "<" instead of "<="
 
  85         $set = $set->complement( $end )   if $open_end;    
 
  93 sub from_datetime_and_duration {
 
  99     # extract datetime parameters
 
 100     for ( qw( start end before after ) ) {
 
 101         if ( exists $args{$_} ) {
 
 103            $dt = delete $args{$_};
 
 107     # extract duration parameters
 
 109     if ( exists $args{duration} ) {
 
 110         $dt_duration = $args{duration};
 
 113         $dt_duration = DateTime::Duration->new( %args );
 
 115     # warn "Creating span from $key => ".$dt->datetime." and $dt_duration";
 
 116     my $other_date = $dt->clone->add_duration( $dt_duration );
 
 117     # warn "Creating span from $key => ".$dt->datetime." and ".$other_date->datetime;
 
 119     if ( $dt_duration->is_positive ) {
 
 120         # check if have to invert keys
 
 121         $key = 'after' if $key eq 'end';
 
 122         $key = 'start' if $key eq 'before';
 
 123         $other_key = 'before';
 
 126         # check if have to invert keys
 
 127         $other_key = 'end' if $key eq 'after';
 
 128         $other_key = 'before' if $key eq 'start';
 
 131     return $class->new( $key => $dt, $other_key => $other_date ); 
 
 134 # This method is intentionally not documented.  It's really only for
 
 135 # use by ::Set and ::SpanSet's as_list() and iterator() methods.
 
 140     # If we find anything _not_ appropriate for from_datetimes, we
 
 141     # assume it must be for durations, and call this constructor.
 
 142     # This way, we don't need to hardcode the DateTime::Duration
 
 144     foreach ( keys %args )
 
 146         return $class->from_datetime_and_duration(%args)
 
 147             unless /^(?:before|after|start|end)$/;
 
 150     return $class->from_datetimes(%args);
 
 155         set => $_[0]->{set}->copy,
 
 159 # Set::Infinite methods
 
 162     my ($set1, $set2) = @_;
 
 163     my $class = ref($set1);
 
 164     my $tmp = {};  # $class->new();
 
 165     $set2 = $set2->as_spanset
 
 166         if $set2->can( 'as_spanset' );
 
 167     $set2 = $set2->as_set
 
 168         if $set2->can( 'as_set' );
 
 169     $set2 = DateTime::Set->from_datetimes( dates => [ $set2 ] ) 
 
 170         unless $set2->can( 'union' );
 
 171     $tmp->{set} = $set1->{set}->intersection( $set2->{set} );
 
 173     # intersection() can generate something more complex than a span.
 
 174     bless $tmp, 'DateTime::SpanSet';
 
 180     my ($set1, $set2) = @_;
 
 181     my $class = ref($set1);
 
 182     $set2 = $set2->as_spanset
 
 183         if $set2->can( 'as_spanset' );
 
 184     $set2 = $set2->as_set
 
 185         if $set2->can( 'as_set' );
 
 186     $set2 = DateTime::Set->from_datetimes( dates => [ $set2 ] ) 
 
 187         unless $set2->can( 'union' );
 
 188     return $set1->{set}->intersects( $set2->{set} );
 
 192     my ($set1, $set2) = @_;
 
 193     my $class = ref($set1);
 
 194     $set2 = $set2->as_spanset
 
 195         if $set2->can( 'as_spanset' );
 
 196     $set2 = $set2->as_set
 
 197         if $set2->can( 'as_set' );
 
 198     $set2 = DateTime::Set->from_datetimes( dates => [ $set2 ] ) 
 
 199         unless $set2->can( 'union' );
 
 200     return $set1->{set}->contains( $set2->{set} );
 
 204     my ($set1, $set2) = @_;
 
 205     my $class = ref($set1);
 
 206     my $tmp = {};   # $class->new();
 
 207     $set2 = $set2->as_spanset
 
 208         if $set2->can( 'as_spanset' );
 
 209     $set2 = $set2->as_set
 
 210         if $set2->can( 'as_set' );
 
 211     $set2 = DateTime::Set->from_datetimes( dates => [ $set2 ] ) 
 
 212         unless $set2->can( 'union' );
 
 213     $tmp->{set} = $set1->{set}->union( $set2->{set} );
 
 215     # union() can generate something more complex than a span.
 
 216     bless $tmp, 'DateTime::SpanSet';
 
 218     # # We have to check it's internal structure to find out.
 
 219     # if ( $#{ $tmp->{set}->{list} } != 0 ) {
 
 220     #    bless $tmp, 'Date::SpanSet';
 
 227     my ($set1, $set2) = @_;
 
 228     my $class = ref($set1);
 
 229     my $tmp = {};   # $class->new;
 
 231         $set2 = $set2->as_spanset
 
 232             if $set2->can( 'as_spanset' );
 
 233         $set2 = $set2->as_set
 
 234             if $set2->can( 'as_set' );
 
 235         $set2 = DateTime::Set->from_datetimes( dates => [ $set2 ] ) 
 
 236             unless $set2->can( 'union' );
 
 237         $tmp->{set} = $set1->{set}->complement( $set2->{set} );
 
 240         $tmp->{set} = $set1->{set}->complement;
 
 243     # complement() can generate something more complex than a span.
 
 244     bless $tmp, 'DateTime::SpanSet';
 
 246     # # We have to check it's internal structure to find out.
 
 247     # if ( $#{ $tmp->{set}->{list} } != 0 ) {
 
 248     #    bless $tmp, 'Date::SpanSet';
 
 255     return DateTime::Set::_fix_datetime( $_[0]->{set}->min );
 
 261     return DateTime::Set::_fix_datetime( $_[0]->{set}->max );
 
 267     # min_a returns info about the set boundary 
 
 268     my ($min, $open) = $_[0]->{set}->min_a;
 
 272 sub start_is_closed { $_[0]->start_is_open ? 0 : 1 }
 
 275     # max_a returns info about the set boundary 
 
 276     my ($max, $open) = $_[0]->{set}->max_a;
 
 280 sub end_is_closed { $_[0]->end_is_open ? 0 : 1 }
 
 291         local $SIG{__DIE__};   # don't want to trap this (rt ticket 5434)
 
 292         $dur = $_[0]->end->subtract_datetime_absolute( $_[0]->start )
 
 295     return $dur if defined $dur;
 
 297     return DateTime::Infinite::Future->new -
 
 298            DateTime::Infinite::Past->new;
 
 308 DateTime::Span - Datetime spans
 
 315     $date1 = DateTime->new( year => 2002, month => 3, day => 11 );
 
 316     $date2 = DateTime->new( year => 2003, month => 4, day => 12 );
 
 317     $set2 = DateTime::Span->from_datetimes( start => $date1, end => $date2 );
 
 318     #  set2 = 2002-03-11 until 2003-04-12
 
 320     $set = $set1->union( $set2 );         # like "OR", "insert", "both"
 
 321     $set = $set1->complement( $set2 );    # like "delete", "remove"
 
 322     $set = $set1->intersection( $set2 );  # like "AND", "while"
 
 323     $set = $set1->complement;             # like "NOT", "negate", "invert"
 
 325     if ( $set1->intersects( $set2 ) ) { ...  # like "touches", "interferes"
 
 326     if ( $set1->contains( $set2 ) ) { ...    # like "is-fully-inside"
 
 329     $date = $set1->start;           # first date of the span
 
 330     $date = $set1->end;             # last date of the span
 
 334 C<DateTime::Span> is a module for handling datetime spans, otherwise
 
 335 known as ranges or periods ("from X to Y, inclusive of all datetimes
 
 338 This is different from a C<DateTime::Set>, which is made of individual
 
 339 datetime points as opposed to a range. There is also a module
 
 340 C<DateTime::SpanSet> to handle sets of spans.
 
 346 =item * from_datetimes
 
 348 Creates a new span based on a starting and ending datetime.
 
 350 A 'closed' span includes its end-dates:
 
 352    $span = DateTime::Span->from_datetimes( start => $dt1, end => $dt2 );
 
 354 An 'open' span does not include its end-dates:
 
 356    $span = DateTime::Span->from_datetimes( after => $dt1, before => $dt2 );
 
 358 A 'semi-open' span includes one of its end-dates:
 
 360    $span = DateTime::Span->from_datetimes( start => $dt1, before => $dt2 );
 
 361    $span = DateTime::Span->from_datetimes( after => $dt1, end => $dt2 );
 
 363 A span might have just a beginning date, or just an ending date.
 
 364 These spans end, or start, in an imaginary 'forever' date:
 
 366    $span = DateTime::Span->from_datetimes( start => $dt1 );
 
 367    $span = DateTime::Span->from_datetimes( end => $dt2 );
 
 368    $span = DateTime::Span->from_datetimes( after => $dt1 );
 
 369    $span = DateTime::Span->from_datetimes( before => $dt2 );
 
 371 You cannot give both a "start" and "after" argument, nor can you give
 
 372 both an "end" and "before" argument.  Either of these conditions will
 
 373 cause the C<from_datetimes()> method to die.
 
 375 To summarize, a datetime passed as either "start" or "end" is included
 
 376 in the span.  A datetime passed as either "after" or "before" is
 
 377 excluded from the span.
 
 379 =item * from_datetime_and_duration
 
 383    $span = DateTime::Span->from_datetime_and_duration( 
 
 384        start => $dt1, duration => $dt_dur1 );
 
 385    $span = DateTime::Span->from_datetime_and_duration( 
 
 386        after => $dt1, hours => 12 );
 
 388 The new "end of the set" is I<open> by default.
 
 392 This object method returns a replica of the given object.
 
 394 =item * set_time_zone( $tz )
 
 396 This method accepts either a time zone object or a string that can be
 
 397 passed as the "name" parameter to C<< DateTime::TimeZone->new() >>.
 
 398 If the new time zone's offset is different from the old time zone,
 
 399 then the I<local> time is adjusted accordingly.
 
 401 If the old time zone was a floating time zone, then no adjustments to
 
 402 the local time are made, except to account for leap seconds.  If the
 
 403 new time zone is floating, then the I<UTC> time is adjusted in order
 
 404 to leave the local time untouched.
 
 408 The total size of the set, as a C<DateTime::Duration> object, or as a
 
 409 scalar containing infinity.
 
 411 Also available as C<size()>.
 
 417 First or last dates in the span.
 
 419 It is possible that the return value from these methods may be a
 
 420 C<DateTime::Infinite::Future> or a C<DateTime::Infinite::Past>xs object.
 
 422 If the set ends C<before> a date C<$dt>, it returns C<$dt>. Note that
 
 423 in this case C<$dt> is not a set element - but it is a set boundary.
 
 427 # scalar containing either negative infinity
 
 428 # or positive infinity.
 
 430 =item * start_is_closed
 
 432 =item * end_is_closed
 
 434 Returns true if the first or last dates belong to the span ( begin <= x <= end ).
 
 436 =item * start_is_open
 
 440 Returns true if the first or last dates are excluded from the span ( begin < x < end ).
 
 448 Set operations may be performed not only with C<DateTime::Span>
 
 449 objects, but also with C<DateTime::Set> and C<DateTime::SpanSet>
 
 450 objects.  These set operations always return a C<DateTime::SpanSet>
 
 453     $set = $span->union( $set2 );         # like "OR", "insert", "both"
 
 454     $set = $span->complement( $set2 );    # like "delete", "remove"
 
 455     $set = $span->intersection( $set2 );  # like "AND", "while"
 
 456     $set = $span->complement;             # like "NOT", "negate", "invert"
 
 462 These set functions return a boolean value.
 
 464     if ( $span->intersects( $set2 ) ) { ...  # like "touches", "interferes"
 
 465     if ( $span->contains( $dt ) ) { ...    # like "is-fully-inside"
 
 467 These methods can accept a C<DateTime>, C<DateTime::Set>,
 
 468 C<DateTime::Span>, or C<DateTime::SpanSet> object as an argument.
 
 474 Support is offered through the C<datetime@perl.org> mailing list.
 
 476 Please report bugs using rt.cpan.org
 
 480 Flavio Soibelmann Glock <fglock@pucrs.br>
 
 482 The API was developed together with Dave Rolsky and the DateTime Community.
 
 486 Copyright (c) 2003-2006 Flavio Soibelmann Glock. All rights reserved.
 
 487 This program is free software; you can distribute it and/or modify it
 
 488 under the same terms as Perl itself.
 
 490 The full text of the license can be found in the LICENSE file
 
 491 included with this module.
 
 497 For details on the Perl DateTime Suite project please see
 
 498 L<http://datetime.perl.org>.