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>.