File Coverage

blib/lib/DateTime/SpanSet.pm
Criterion Covered Total %
statement 222 282 78.7
branch 87 158 55.0
condition 14 33 42.4
subroutine 31 41 75.6
pod 28 28 100.0
total 382 542 70.4


line stmt bran cond sub pod time code
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.
4              
5             package DateTime::SpanSet;
6              
7 23     23   263631 use strict;
  23         28  
  23         587  
8              
9 23     23   1365 use DateTime::Set;
  23         120  
  23         346  
10 23     23   68 use DateTime::Infinite;
  23         19  
  23         405  
11              
12 23     23   62 use Carp;
  23         19  
  23         1144  
13 23     23   86 use Params::Validate qw( validate SCALAR BOOLEAN OBJECT CODEREF ARRAYREF );
  23         21  
  23         1161  
14 23     23   77 use vars qw( $VERSION );
  23         22  
  23         1128  
15              
16 23     23   78 use constant INFINITY => 100 ** 100 ** 100 ;
  23         28  
  23         1501  
17 23     23   76 use constant NEG_INFINITY => -1 * (100 ** 100 ** 100);
  23         26  
  23         55234  
18             $VERSION = $DateTime::Set::VERSION;
19              
20             sub iterate {
21 0     0 1 0 my ( $self, $callback ) = @_;
22 0         0 my $class = ref( $self );
23 0         0 my $return = $class->empty_set;
24             $return->{set} = $self->{set}->iterate(
25             sub {
26 0     0   0 my $span = bless { set => $_[0] }, 'DateTime::Span';
27 0         0 $callback->( $span->clone );
28             $span = $span->{set}
29 0 0       0 if UNIVERSAL::can( $span, 'union' );
30 0         0 return $span;
31             }
32 0         0 );
33 0         0 $return;
34             }
35              
36             sub map {
37 1     1 1 30 my ( $self, $callback ) = @_;
38 1         2 my $class = ref( $self );
39 1 50       5 die "The callback parameter to map() must be a subroutine reference"
40             unless ref( $callback ) eq 'CODE';
41 1         4 my $return = $class->empty_set;
42             $return->{set} = $self->{set}->iterate(
43             sub {
44 2     2   86 local $_ = bless { set => $_[0]->clone }, 'DateTime::Span';
45 2         62 my @list = $callback->();
46 2         22 my $set = $class->empty_set;
47 2         45 $set = $set->union( $_ ) for @list;
48 2         15 return $set->{set};
49             }
50 1         33 );
51 1         99 $return;
52             }
53              
54             sub grep {
55 0     0 1 0 my ( $self, $callback ) = @_;
56 0         0 my $class = ref( $self );
57 0 0       0 die "The callback parameter to grep() must be a subroutine reference"
58             unless ref( $callback ) eq 'CODE';
59 0         0 my $return = $class->empty_set;
60             $return->{set} = $self->{set}->iterate(
61             sub {
62 0     0   0 local $_ = bless { set => $_[0]->clone }, 'DateTime::Span';
63 0         0 my $result = $callback->();
64 0 0 0     0 return $_->{set} if $result && $_;
65 0         0 return;
66             }
67 0         0 );
68 0         0 $return;
69             }
70              
71             sub set_time_zone {
72 0     0 1 0 my ( $self, $tz ) = @_;
73              
74             # TODO - use iterate() instead
75              
76             my $result = $self->{set}->iterate(
77             sub {
78 0     0   0 my %tmp = %{ $_[0]->{list}[0] };
  0         0  
79 0 0       0 $tmp{a} = $tmp{a}->clone->set_time_zone( $tz ) if ref $tmp{a};
80 0 0       0 $tmp{b} = $tmp{b}->clone->set_time_zone( $tz ) if ref $tmp{b};
81 0         0 \%tmp;
82             },
83             backtrack_callback => sub {
84 0     0   0 my ( $min, $max ) = ( $_[0]->min, $_[0]->max );
85 0 0       0 if ( ref($min) )
86             {
87 0         0 $min = $min->clone;
88 0         0 $min->set_time_zone( 'floating' );
89             }
90 0 0       0 if ( ref($max) )
91             {
92 0         0 $max = $max->clone;
93 0         0 $max->set_time_zone( 'floating' );
94             }
95 0         0 return Set::Infinite::_recurrence->new( $min, $max );
96             },
97 0         0 );
98              
99             ### this code enables 'subroutine method' behaviour
100 0         0 $self->{set} = $result;
101 0         0 return $self;
102             }
103              
104             sub from_spans {
105 5     5 1 279 my $class = shift;
106 5         83 my %args = validate( @_,
107             { spans =>
108             { type => ARRAYREF,
109             optional => 1,
110             },
111             }
112             );
113 5         15 my $self = {};
114 5         19 my $set = Set::Infinite::_recurrence->new();
115 5         62 $set = $set->union( $_->{set} ) for @{ $args{spans} };
  5         25  
116 5         227 $self->{set} = $set;
117 5         9 bless $self, $class;
118 5         17 return $self;
119             }
120              
121             sub from_set_and_duration {
122             # set => $dt_set, days => 1
123 3     3 1 22 my $class = shift;
124 3         8 my %args = @_;
125             my $set = delete $args{set} ||
126 3   33     10 carp "from_set_and_duration needs a 'set' parameter";
127              
128 3 50       30 $set = $set->as_set
129             if UNIVERSAL::can( $set, 'as_set' );
130 3 50       19 unless ( UNIVERSAL::can( $set, 'union' ) ) {
131 0         0 carp "'set' must be a set" };
132              
133             my $duration = delete $args{duration} ||
134 3   66     15 new DateTime::Duration( %args );
135 3         88 my $end_set = $set->clone->add_duration( $duration );
136 3         11 return $class->from_sets( start_set => $set,
137             end_set => $end_set );
138             }
139              
140             sub from_sets {
141 13     13 1 1228 my $class = shift;
142 13         200 my %args = validate( @_,
143             { start_set =>
144             { # can => 'union',
145             optional => 0,
146             },
147             end_set =>
148             { # can => 'union',
149             optional => 0,
150             },
151             }
152             );
153 13         46 my $start_set = delete $args{start_set};
154 13         14 my $end_set = delete $args{end_set};
155              
156 13 50       49 $start_set = $start_set->as_set
157             if UNIVERSAL::can( $start_set, 'as_set' );
158 13 50       28 $end_set = $end_set->as_set
159             if UNIVERSAL::can( $end_set, 'as_set' );
160              
161 13 50       32 unless ( UNIVERSAL::can( $start_set, 'union' ) ) {
162 0         0 carp "'start_set' must be a set" };
163 13 50       28 unless ( UNIVERSAL::can( $end_set, 'union' ) ) {
164 0         0 carp "'end_set' must be a set" };
165              
166 13         9 my $self;
167             $self->{set} = $start_set->{set}->until(
168 13         46 $end_set->{set} );
169 13         1258 bless $self, $class;
170 13         37 return $self;
171             }
172              
173             sub start_set {
174 8 100 66 8 1 490 if ( exists $_[0]->{set}{method} &&
175             $_[0]->{set}{method} eq 'until' )
176             {
177 5         16 return bless { set => $_[0]->{set}{parent}[0] }, 'DateTime::Set';
178             }
179 3         9 my $return = DateTime::Set->empty_set;
180 3         48 $return->{set} = $_[0]->{set}->start_set;
181 3         398 $return;
182             }
183              
184             sub end_set {
185 7 100 66 7 1 759 if ( exists $_[0]->{set}{method} &&
186             $_[0]->{set}{method} eq 'until' )
187             {
188 5         10 return bless { set => $_[0]->{set}{parent}[1] }, 'DateTime::Set';
189             }
190 2         5 my $return = DateTime::Set->empty_set;
191 2         30 $return->{set} = $_[0]->{set}->end_set;
192 2         267 $return;
193             }
194              
195             sub empty_set {
196 34     34 1 31 my $class = shift;
197              
198 34         69 return bless { set => Set::Infinite::_recurrence->new }, $class;
199             }
200              
201             sub is_empty_set {
202 0     0 1 0 my $set = $_[0];
203 0         0 $set->{set}->is_null;
204             }
205              
206             sub clone {
207             bless {
208             set => $_[0]->{set}->copy,
209 9     9 1 25 }, ref $_[0];
210             }
211              
212              
213             sub iterator {
214 12     12 1 601 my $self = shift;
215              
216 12         17 my %args = @_;
217 12         11 my $span;
218 12         13 $span = delete $args{span};
219 12 100       25 $span = DateTime::Span->new( %args ) if %args;
220              
221 12 100       26 return $self->intersection( $span ) if $span;
222 8         16 return $self->clone;
223             }
224              
225              
226             # next() gets the next element from an iterator()
227             sub next {
228 48     48 1 4374 my ($self) = shift;
229              
230             # TODO: this is fixing an error from elsewhere
231             # - find out what's going on! (with "sunset.pl")
232 48 100       106 return undef unless ref $self->{set};
233              
234 45 100       62 if ( @_ )
235             {
236 6         6 my $max;
237 6 100       18 $max = $_[0]->max if UNIVERSAL::can( $_[0], 'union' );
238 6 100       20 $max = $_[0] if ! defined $max;
239              
240 6 50 33     10 return undef if ! ref( $max ) && $max == INFINITY;
241              
242 6         17 my $span = DateTime::Span->from_datetimes( start => $max );
243 6         11 my $iterator = $self->intersection( $span );
244 6         16 my $return = $iterator->next;
245              
246 6 50       10 return $return if ! defined $return;
247 6 100       17 return $return if ! $return->intersects( $max );
248              
249 1         213 return $iterator->next;
250             }
251              
252 39         79 my ($head, $tail) = $self->{set}->first;
253 39         2410 $self->{set} = $tail;
254 39 50       123 return $head unless ref $head;
255 39         48 my $return = {
256             set => $head,
257             };
258 39         44 bless $return, 'DateTime::Span';
259 39         85 return $return;
260             }
261              
262             # previous() gets the last element from an iterator()
263             sub previous {
264 17     17 1 695 my ($self) = shift;
265              
266 17 100       39 return undef unless ref $self->{set};
267              
268 15 100       26 if ( @_ )
269             {
270 3         2 my $min;
271 3 100       11 $min = $_[0]->min if UNIVERSAL::can( $_[0], 'union' );
272 3 100       12 $min = $_[0] if ! defined $min;
273              
274 3 50 33     8 return undef if ! ref( $min ) && $min == INFINITY;
275              
276 3         7 my $span = DateTime::Span->from_datetimes( end => $min );
277 3         5 my $iterator = $self->intersection( $span );
278 3         7 my $return = $iterator->previous;
279              
280 3 50       4 return $return if ! defined $return;
281 3 100       7 return $return if ! $return->intersects( $min );
282              
283 2         435 return $iterator->previous;
284             }
285              
286 12         30 my ($head, $tail) = $self->{set}->last;
287 12         409 $self->{set} = $tail;
288 12 50       32 return $head unless ref $head;
289 12         14 my $return = {
290             set => $head,
291             };
292 12         13 bless $return, 'DateTime::Span';
293 12         16 return $return;
294             }
295              
296             # "current" means less-or-equal to a DateTime
297             sub current {
298 4     4 1 312 my $self = shift;
299              
300 4         5 my $previous;
301             my $next;
302             {
303 4         4 my $min;
  4         1  
304 4 50       15 $min = $_[0]->min if UNIVERSAL::can( $_[0], 'union' );
305 4 50       7 $min = $_[0] if ! defined $min;
306 4 50 33     8 return undef if ! ref( $min ) && $min == INFINITY;
307 4         11 my $span = DateTime::Span->from_datetimes( end => $min );
308 4         6 my $iterator = $self->intersection( $span );
309 4         6 $previous = $iterator->previous;
310 4         9 $span = DateTime::Span->from_datetimes( start => $min );
311 4         12 $iterator = $self->intersection( $span );
312 4         20 $next = $iterator->next;
313             }
314 4 50       8 return $previous unless defined $next;
315              
316 4 50       11 my $dt1 = defined $previous
317             ? $next->union( $previous )
318             : $next;
319              
320 4         7 my $return = $dt1->intersected_spans( $_[0] );
321              
322 4 100       7 $return = $previous
323             if !defined $return->max;
324              
325 4 50       25 bless $return, 'DateTime::SpanSet'
326             if defined $return;
327 4         16 return $return;
328             }
329              
330             sub closest {
331 3     3 1 1133 my $self = shift;
332 3         4 my $dt = shift;
333              
334 3         4 my $dt1 = $self->current( $dt );
335 3         5 my $dt2 = $self->next( $dt );
336 3 100       184 bless $dt2, 'DateTime::SpanSet'
337             if defined $dt2;
338              
339 3 50       5 return $dt2 unless defined $dt1;
340 3 100       8 return $dt1 unless defined $dt2;
341              
342 2 50       9 $dt = DateTime::Set->from_datetimes( dates => [ $dt ] )
343             unless UNIVERSAL::can( $dt, 'union' );
344              
345 2 50       5 return $dt1 if $dt1->contains( $dt );
346              
347 2         350 my $delta = $dt->min - $dt1->max;
348 2 100       404 return $dt1 if ( $dt2->min - $delta ) >= $dt->max;
349              
350 1         72 return $dt2;
351             }
352              
353             sub as_list {
354 1     1 1 595 my $self = shift;
355 1 50       6 return undef unless ref( $self->{set} );
356              
357 1         2 my %args = @_;
358 1         2 my $span;
359 1         1 $span = delete $args{span};
360 1 50       3 $span = DateTime::Span->new( %args ) if %args;
361              
362 1         3 my $set = $self->clone;
363 1 50       23 $set = $set->intersection( $span ) if $span;
364              
365             # Note: removing this line means we may end up in an infinite loop!
366 1 50       6 return undef if $set->{set}->is_too_complex; # undef = no start/end
367              
368             # return if $set->{set}->is_null; # nothing = empty
369 1         4 my @result;
370             # we should extract _copies_ of the set elements,
371             # such that the user can't modify the set indirectly
372              
373 1         2 my $iter = $set->iterator;
374 1         19 while ( my $dt = $iter->next )
375             {
376 3 50       6 push @result, $dt
377             if ref( $dt ); # we don't want to return INFINITY value
378             };
379              
380 1         4 return @result;
381             }
382              
383             # Set::Infinite methods
384              
385             sub intersection {
386 22     22 1 26 my ($set1, $set2) = ( shift, shift );
387 22         43 my $class = ref($set1);
388 22         38 my $tmp = $class->empty_set();
389 22 50       267 $set2 = $set2->as_spanset
390             if $set2->can( 'as_spanset' );
391 22 50       50 $set2 = $set2->as_set
392             if $set2->can( 'as_set' );
393 22 50       54 $set2 = DateTime::Set->from_datetimes( dates => [ $set2, @_ ] )
394             unless $set2->can( 'union' );
395 22         63 $tmp->{set} = $set1->{set}->intersection( $set2->{set} );
396 22         7372 return $tmp;
397             }
398              
399             sub intersected_spans {
400 5     5 1 286 my ($set1, $set2) = ( shift, shift );
401 5         6 my $class = ref($set1);
402 5         8 my $tmp = $class->empty_set();
403 5 50       63 $set2 = $set2->as_spanset
404             if $set2->can( 'as_spanset' );
405 5 50       13 $set2 = $set2->as_set
406             if $set2->can( 'as_set' );
407 5 50       23 $set2 = DateTime::Set->from_datetimes( dates => [ $set2, @_ ] )
408             unless $set2->can( 'union' );
409 5         17 $tmp->{set} = $set1->{set}->intersected_spans( $set2->{set} );
410 5         994 return $tmp;
411             }
412              
413             sub intersects {
414 1     1 1 135 my ($set1, $set2) = ( shift, shift );
415            
416 1 50       5 unless ( $set2->can( 'union' ) )
417             {
418 1         3 for ( $set2, @_ )
419             {
420 1 50       2 return 1 if $set1->contains( $_ );
421             }
422 1         48 return 0;
423             }
424            
425 0         0 my $class = ref($set1);
426 0 0       0 $set2 = $set2->as_spanset
427             if $set2->can( 'as_spanset' );
428 0 0       0 $set2 = $set2->as_set
429             if $set2->can( 'as_set' );
430 0 0       0 $set2 = DateTime::Set->from_datetimes( dates => [ $set2, @_ ] )
431             unless $set2->can( 'union' );
432 0         0 return $set1->{set}->intersects( $set2->{set} );
433             }
434              
435             sub contains {
436 6     6 1 579 my ($set1, $set2) = ( shift, shift );
437            
438 6 100       22 unless ( $set2->can( 'union' ) )
439             {
440 4 50 33     22 if ( exists $set1->{set}{method} &&
441             $set1->{set}{method} eq 'until' )
442             {
443 4         7 my $start_set = $set1->start_set;
444 4         7 my $end_set = $set1->end_set;
445              
446 4         7 for ( $set2, @_ )
447             {
448 4         9 my $start = $start_set->next( $set2 );
449 4         17 my $end = $end_set->next( $set2 );
450              
451 4 50 33     43 goto ABORT unless defined $start && defined $end;
452            
453 4 100       8 return 0 if $start < $end;
454             }
455 1         44 return 1;
456              
457 0         0 ABORT: ;
458             # don't know
459             }
460             }
461            
462 2         3 my $class = ref($set1);
463 2 50       6 $set2 = $set2->as_spanset
464             if $set2->can( 'as_spanset' );
465 2 50       6 $set2 = $set2->as_set
466             if $set2->can( 'as_set' );
467 2 50       4 $set2 = DateTime::Set->from_datetimes( dates => [ $set2, @_ ] )
468             unless $set2->can( 'union' );
469 2         6 return $set1->{set}->contains( $set2->{set} );
470             }
471              
472             sub union {
473 4     4 1 5 my ($set1, $set2) = ( shift, shift );
474 4         6 my $class = ref($set1);
475 4         6 my $tmp = $class->empty_set();
476 4 50       64 $set2 = $set2->as_spanset
477             if $set2->can( 'as_spanset' );
478 4 50       12 $set2 = $set2->as_set
479             if $set2->can( 'as_set' );
480 4 100       18 $set2 = DateTime::Set->from_datetimes( dates => [ $set2, @_ ] )
481             unless $set2->can( 'union' );
482 4         15 $tmp->{set} = $set1->{set}->union( $set2->{set} );
483 4         653 return $tmp;
484             }
485              
486             sub complement {
487 0     0 1 0 my ($set1, $set2) = ( shift, shift );
488 0         0 my $class = ref($set1);
489 0         0 my $tmp = $class->empty_set();
490 0 0       0 if (defined $set2) {
491 0 0       0 $set2 = $set2->as_spanset
492             if $set2->can( 'as_spanset' );
493 0 0       0 $set2 = $set2->as_set
494             if $set2->can( 'as_set' );
495 0 0       0 $set2 = DateTime::Set->from_datetimes( dates => [ $set2, @_ ] )
496             unless $set2->can( 'union' );
497 0         0 $tmp->{set} = $set1->{set}->complement( $set2->{set} );
498             }
499             else {
500 0         0 $tmp->{set} = $set1->{set}->complement;
501             }
502 0         0 return $tmp;
503             }
504              
505             sub start {
506 16     16 1 927 return DateTime::Set::_fix_datetime( $_[0]->{set}->min );
507             }
508              
509             *min = \&start;
510              
511             sub end {
512 20     20 1 1367 return DateTime::Set::_fix_datetime( $_[0]->{set}->max );
513             }
514              
515             *max = \&end;
516              
517             # returns a DateTime::Span
518             sub span {
519 0     0 1 0 my $set = $_[0]->{set}->span;
520 0         0 my $self = bless { set => $set }, 'DateTime::Span';
521 0         0 return $self;
522             }
523              
524             # returns a DateTime::Duration
525             sub duration {
526 3     3 1 373 my $dur;
527              
528             return DateTime::Duration->new( seconds => 0 )
529 3 100       17 if $_[0]->{set}->is_empty;
530              
531 2         23 local $@;
532 2         3 eval {
533 2         5 local $SIG{__DIE__}; # don't want to trap this (rt ticket 5434)
534             $dur = $_[0]->{set}->size
535 2         9 };
536              
537 2 50 66     230 return $dur if defined $dur && ref( $dur );
538 2         6 return DateTime::Infinite::Future->new -
539             DateTime::Infinite::Past->new;
540             # return INFINITY;
541             }
542             *size = \&duration;
543              
544             1;
545              
546             __END__