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   262489 use strict;
  23         22  
  23         486  
8              
9 23     23   1241 use DateTime::Set;
  23         114  
  23         314  
10 23     23   69 use DateTime::Infinite;
  23         16  
  23         374  
11              
12 23     23   54 use Carp;
  23         18  
  23         992  
13 23     23   72 use Params::Validate qw( validate SCALAR BOOLEAN OBJECT CODEREF ARRAYREF );
  23         19  
  23         1028  
14 23     23   67 use vars qw( $VERSION );
  23         20  
  23         1000  
15              
16 23     23   74 use constant INFINITY => 100 ** 100 ** 100 ;
  23         21  
  23         1287  
17 23     23   70 use constant NEG_INFINITY => -1 * (100 ** 100 ** 100);
  23         28  
  23         51621  
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 12 my ( $self, $callback ) = @_;
38 1         16 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         22 my $return = $class->empty_set;
42             $return->{set} = $self->{set}->iterate(
43             sub {
44 2     2   63 local $_ = bless { set => $_[0]->clone }, 'DateTime::Span';
45 2         47 my @list = $callback->();
46 2         14 my $set = $class->empty_set;
47 2         33 $set = $set->union( $_ ) for @list;
48 2         15 return $set->{set};
49             }
50 1         23 );
51 1         112 $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 286 my $class = shift;
106 5         78 my %args = validate( @_,
107             { spans =>
108             { type => ARRAYREF,
109             optional => 1,
110             },
111             }
112             );
113 5         16 my $self = {};
114 5         33 my $set = Set::Infinite::_recurrence->new();
115 5         54 $set = $set->union( $_->{set} ) for @{ $args{spans} };
  5         22  
116 5         220 $self->{set} = $set;
117 5         8 bless $self, $class;
118 5         11 return $self;
119             }
120              
121             sub from_set_and_duration {
122             # set => $dt_set, days => 1
123 3     3 1 19 my $class = shift;
124 3         6 my %args = @_;
125             my $set = delete $args{set} ||
126 3   33     12 carp "from_set_and_duration needs a 'set' parameter";
127              
128 3 50       25 $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     12 new DateTime::Duration( %args );
135 3         86 my $end_set = $set->clone->add_duration( $duration );
136 3         10 return $class->from_sets( start_set => $set,
137             end_set => $end_set );
138             }
139              
140             sub from_sets {
141 13     13 1 1562 my $class = shift;
142 13         191 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         51 my $start_set = delete $args{start_set};
154 13         13 my $end_set = delete $args{end_set};
155              
156 13 50       62 $start_set = $start_set->as_set
157             if UNIVERSAL::can( $start_set, 'as_set' );
158 13 50       31 $end_set = $end_set->as_set
159             if UNIVERSAL::can( $end_set, 'as_set' );
160              
161 13 50       30 unless ( UNIVERSAL::can( $start_set, 'union' ) ) {
162 0         0 carp "'start_set' must be a set" };
163 13 50       26 unless ( UNIVERSAL::can( $end_set, 'union' ) ) {
164 0         0 carp "'end_set' must be a set" };
165              
166 13         12 my $self;
167             $self->{set} = $start_set->{set}->until(
168 13         44 $end_set->{set} );
169 13         1295 bless $self, $class;
170 13         33 return $self;
171             }
172              
173             sub start_set {
174 8 100 66 8 1 471 if ( exists $_[0]->{set}{method} &&
175             $_[0]->{set}{method} eq 'until' )
176             {
177 5         15 return bless { set => $_[0]->{set}{parent}[0] }, 'DateTime::Set';
178             }
179 3         10 my $return = DateTime::Set->empty_set;
180 3         49 $return->{set} = $_[0]->{set}->start_set;
181 3         455 $return;
182             }
183              
184             sub end_set {
185 7 100 66 7 1 956 if ( exists $_[0]->{set}{method} &&
186             $_[0]->{set}{method} eq 'until' )
187             {
188 5         11 return bless { set => $_[0]->{set}{parent}[1] }, 'DateTime::Set';
189             }
190 2         7 my $return = DateTime::Set->empty_set;
191 2         32 $return->{set} = $_[0]->{set}->end_set;
192 2         268 $return;
193             }
194              
195             sub empty_set {
196 34     34 1 29 my $class = shift;
197              
198 34         63 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 24 }, ref $_[0];
210             }
211              
212              
213             sub iterator {
214 12     12 1 353 my $self = shift;
215              
216 12         14 my %args = @_;
217 12         7 my $span;
218 12         14 $span = delete $args{span};
219 12 100       25 $span = DateTime::Span->new( %args ) if %args;
220              
221 12 100       27 return $self->intersection( $span ) if $span;
222 8         11 return $self->clone;
223             }
224              
225              
226             # next() gets the next element from an iterator()
227             sub next {
228 48     48 1 6297 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       105 return undef unless ref $self->{set};
233              
234 45 100       71 if ( @_ )
235             {
236 6         6 my $max;
237 6 100       19 $max = $_[0]->max if UNIVERSAL::can( $_[0], 'union' );
238 6 100       17 $max = $_[0] if ! defined $max;
239              
240 6 50 33     13 return undef if ! ref( $max ) && $max == INFINITY;
241              
242 6         16 my $span = DateTime::Span->from_datetimes( start => $max );
243 6         8 my $iterator = $self->intersection( $span );
244 6         17 my $return = $iterator->next;
245              
246 6 50       10 return $return if ! defined $return;
247 6 100       16 return $return if ! $return->intersects( $max );
248              
249 1         216 return $iterator->next;
250             }
251              
252 39         81 my ($head, $tail) = $self->{set}->first;
253 39         2385 $self->{set} = $tail;
254 39 50       122 return $head unless ref $head;
255 39         46 my $return = {
256             set => $head,
257             };
258 39         34 bless $return, 'DateTime::Span';
259 39         87 return $return;
260             }
261              
262             # previous() gets the last element from an iterator()
263             sub previous {
264 17     17 1 766 my ($self) = shift;
265              
266 17 100       40 return undef unless ref $self->{set};
267              
268 15 100       23 if ( @_ )
269             {
270 3         1 my $min;
271 3 100       15 $min = $_[0]->min if UNIVERSAL::can( $_[0], 'union' );
272 3 100       12 $min = $_[0] if ! defined $min;
273              
274 3 50 33     7 return undef if ! ref( $min ) && $min == INFINITY;
275              
276 3         7 my $span = DateTime::Span->from_datetimes( end => $min );
277 3         6 my $iterator = $self->intersection( $span );
278 3         7 my $return = $iterator->previous;
279              
280 3 50       6 return $return if ! defined $return;
281 3 100       6 return $return if ! $return->intersects( $min );
282              
283 2         441 return $iterator->previous;
284             }
285              
286 12         35 my ($head, $tail) = $self->{set}->last;
287 12         423 $self->{set} = $tail;
288 12 50       27 return $head unless ref $head;
289 12         15 my $return = {
290             set => $head,
291             };
292 12         14 bless $return, 'DateTime::Span';
293 12         18 return $return;
294             }
295              
296             # "current" means less-or-equal to a DateTime
297             sub current {
298 4     4 1 391 my $self = shift;
299              
300 4         5 my $previous;
301             my $next;
302             {
303 4         3 my $min;
  4         3  
304 4 50       14 $min = $_[0]->min if UNIVERSAL::can( $_[0], 'union' );
305 4 50       9 $min = $_[0] if ! defined $min;
306 4 50 33     9 return undef if ! ref( $min ) && $min == INFINITY;
307 4         10 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         11 $iterator = $self->intersection( $span );
312 4         14 $next = $iterator->next;
313             }
314 4 50       6 return $previous unless defined $next;
315              
316 4 50       15 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       26 bless $return, 'DateTime::SpanSet'
326             if defined $return;
327 4         14 return $return;
328             }
329              
330             sub closest {
331 3     3 1 1453 my $self = shift;
332 3         4 my $dt = shift;
333              
334 3         5 my $dt1 = $self->current( $dt );
335 3         4 my $dt2 = $self->next( $dt );
336 3 100       180 bless $dt2, 'DateTime::SpanSet'
337             if defined $dt2;
338              
339 3 50       7 return $dt2 unless defined $dt1;
340 3 100       6 return $dt1 unless defined $dt2;
341              
342 2 50       11 $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         352 my $delta = $dt->min - $dt1->max;
348 2 100       389 return $dt1 if ( $dt2->min - $delta ) >= $dt->max;
349              
350 1         53 return $dt2;
351             }
352              
353             sub as_list {
354 1     1 1 359 my $self = shift;
355 1 50       6 return undef unless ref( $self->{set} );
356              
357 1         3 my %args = @_;
358 1         1 my $span;
359 1         1 $span = delete $args{span};
360 1 50       3 $span = DateTime::Span->new( %args ) if %args;
361              
362 1         2 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       7 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       8 push @result, $dt
377             if ref( $dt ); # we don't want to return INFINITY value
378             };
379              
380 1         5 return @result;
381             }
382              
383             # Set::Infinite methods
384              
385             sub intersection {
386 22     22 1 24 my ($set1, $set2) = ( shift, shift );
387 22         23 my $class = ref($set1);
388 22         35 my $tmp = $class->empty_set();
389 22 50       260 $set2 = $set2->as_spanset
390             if $set2->can( 'as_spanset' );
391 22 50       62 $set2 = $set2->as_set
392             if $set2->can( 'as_set' );
393 22 50       52 $set2 = DateTime::Set->from_datetimes( dates => [ $set2, @_ ] )
394             unless $set2->can( 'union' );
395 22         61 $tmp->{set} = $set1->{set}->intersection( $set2->{set} );
396 22         7714 return $tmp;
397             }
398              
399             sub intersected_spans {
400 5     5 1 383 my ($set1, $set2) = ( shift, shift );
401 5         6 my $class = ref($set1);
402 5         9 my $tmp = $class->empty_set();
403 5 50       65 $set2 = $set2->as_spanset
404             if $set2->can( 'as_spanset' );
405 5 50       18 $set2 = $set2->as_set
406             if $set2->can( 'as_set' );
407 5 50       25 $set2 = DateTime::Set->from_datetimes( dates => [ $set2, @_ ] )
408             unless $set2->can( 'union' );
409 5         14 $tmp->{set} = $set1->{set}->intersected_spans( $set2->{set} );
410 5         1101 return $tmp;
411             }
412              
413             sub intersects {
414 1     1 1 142 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         52 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 673 my ($set1, $set2) = ( shift, shift );
437            
438 6 100       26 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         6 my $end_set = $set1->end_set;
445              
446 4         9 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     44 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       5 $set2 = $set2->as_set
466             if $set2->can( 'as_set' );
467 2 50       6 $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         4 my $class = ref($set1);
475 4         6 my $tmp = $class->empty_set();
476 4 50       49 $set2 = $set2->as_spanset
477             if $set2->can( 'as_spanset' );
478 4 50       11 $set2 = $set2->as_set
479             if $set2->can( 'as_set' );
480 4 100       15 $set2 = DateTime::Set->from_datetimes( dates => [ $set2, @_ ] )
481             unless $set2->can( 'union' );
482 4         10 $tmp->{set} = $set1->{set}->union( $set2->{set} );
483 4         637 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 1052 return DateTime::Set::_fix_datetime( $_[0]->{set}->min );
507             }
508              
509             *min = \&start;
510              
511             sub end {
512 20     20 1 1653 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 566 my $dur;
527              
528             return DateTime::Duration->new( seconds => 0 )
529 3 100       18 if $_[0]->{set}->is_empty;
530              
531 2         25 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         10 };
536              
537 2 50 66     238 return $dur if defined $dur && ref( $dur );
538 2         7 return DateTime::Infinite::Future->new -
539             DateTime::Infinite::Past->new;
540             # return INFINITY;
541             }
542             *size = \&duration;
543              
544             1;
545              
546             __END__