File Coverage

blib/lib/DateTime/Event/ICal.pm
Criterion Covered Total %
statement 353 365 96.7
branch 218 282 77.3
condition 20 27 74.0
subroutine 26 26 100.0
pod 1 1 100.0
total 618 701 88.1


line stmt bran cond sub pod time code
1             package DateTime::Event::ICal;
2              
3 7     7   1935714 use strict;
  7         21  
  7         312  
4             require Exporter;
5 7     7   78 use Carp;
  7         15  
  7         536  
6 7     7   1426 use DateTime;
  7         192672  
  7         148  
7 7     7   6506 use DateTime::Set;
  7         369468  
  7         244  
8 7     7   81 use DateTime::Span;
  7         15  
  7         159  
9 7     7   37 use DateTime::SpanSet;
  7         17  
  7         190  
10 7     7   8211 use DateTime::Event::Recurrence 0.11;
  7         50370  
  7         287  
11 7     7   80 use Params::Validate qw(:all);
  7         18  
  7         1673  
12 7     7   53 use vars qw( $VERSION @ISA );
  7         17  
  7         599  
13             @ISA = qw( Exporter );
14             $VERSION = '0.12';
15              
16 7     7   125 use constant INFINITY => 100 ** 100 ** 100 ;
  7         14  
  7         498  
17 7     7   38 use constant NEG_INFINITY => -1 * (100 ** 100 ** 100);
  7         13  
  7         19263  
18              
19             my %weekdays = ( mo => 1, tu => 2, we => 3, th => 4,
20             fr => 5, sa => 6, su => 7 );
21              
22             my %freqs = (
23             secondly => { name => 'second', names => 'seconds' },
24             minutely => { name => 'minute', names => 'minutes' },
25             hourly => { name => 'hour', names => 'hours' },
26             daily => { name => 'day', names => 'days' },
27             monthly => { name => 'month', names => 'months' },
28             weekly => { name => 'week', names => 'weeks' },
29             yearly => { name => 'year', names => 'years' },
30             );
31              
32             # internal debugging method - formats the argument list for error messages
33             # the output from this routine is also used by DateTime::Format::ICal->format_recurrence()
34             sub _param_str {
35 67     67   247 my %param = @_;
36 67         134 my @str;
37 67         556 for ( qw( freq interval count ),
38             qw( byyear bymonth byday ),
39             sort keys %param )
40             {
41 591 100       1528 next unless exists $param{$_};
42 189 100       1439 if ( ref( $param{$_} ) eq 'ARRAY' ) {
    100          
    50          
43 65         208 push @str, "$_=". join( ',', @{$param{$_}} )
  65         256  
44             }
45             elsif ( UNIVERSAL::can( $param{$_}, 'datetime' ) ) {
46 8 50       26 if ( $DateTime::Format::ICal::VERSION ) {
47 0         0 push @str, "$_=" . DateTime::Format::ICal->format_datetime( $param{$_} );
48             }
49             else {
50 8         37 push @str, "$_=". $param{$_}->datetime
51             }
52             }
53             elsif ( defined $param{$_} ) {
54 116         379 push @str, "$_=". $param{$_}
55             }
56             else {
57 0         0 push @str, "$_=undef"
58             }
59 189         506 delete $param{$_};
60             }
61 67         390 return join(';', @str);
62             }
63              
64             # recurrence constructors
65              
66             sub _secondly_recurrence {
67 1     1   2 my ($dtstart, $argsref) = @_;
68 1         2 my %by;
69 1         4 my %args = %$argsref;
70 1 50       7 $by{interval} = $args{interval} if exists $args{interval};
71 1         3 $by{start} = $dtstart;
72             delete $$argsref{$_}
73 1         5 for qw( interval );
74 1         9 return DateTime::Event::Recurrence->secondly( %by );
75             }
76              
77             sub _minutely_recurrence {
78 7     7   20 my ($dtstart, $argsref) = @_;
79 7         14 my %by;
80 7         38 my %args = %$argsref;
81 7 50       45 $by{interval} = $args{interval} if exists $args{interval};
82 7         19 $by{start} = $dtstart;
83 7 50       30 $by{seconds} = $args{bysecond} if exists $args{bysecond};
84 7 50       60 $by{seconds} = $dtstart->second unless exists $by{seconds};
85             delete $$argsref{$_}
86 7         80 for qw( interval bysecond );
87 7         78 return DateTime::Event::Recurrence->minutely( %by );
88             }
89              
90             sub _hourly_recurrence {
91 3     3   7 my ($dtstart, $argsref) = @_;
92 3         6 my %by;
93 3         14 my %args = %$argsref;
94 3 50       20 $by{interval} = $args{interval} if exists $args{interval};
95 3         9 $by{start} = $dtstart;
96 3 100       14 $by{seconds} = $args{bysecond} if exists $args{bysecond};
97 3 100       18 $by{seconds} = $dtstart->second unless exists $by{seconds};
98 3 50       1013 $by{minutes} = $args{byminute} if exists $args{byminute};
99 3 50       21 $by{minutes} = $dtstart->minute unless exists $by{minutes};
100             delete $$argsref{$_}
101 3         38 for qw( interval byminute bysecond );
102 3         25 return DateTime::Event::Recurrence->hourly( %by );
103             }
104              
105             sub _daily_recurrence {
106 10     10   19 my ($dtstart, $argsref) = @_;
107 10         22 my %by;
108 10         44 my %args = %$argsref;
109 10 100       54 $by{interval} = $args{interval} if exists $args{interval};
110 10         91 $by{start} = $dtstart;
111 10 50       1283 $by{seconds} = $args{bysecond} if exists $args{bysecond};
112 10 50       74 $by{seconds} = $dtstart->second unless exists $by{seconds};
113 10 100       91 $by{minutes} = $args{byminute} if exists $args{byminute};
114 10 100       59 $by{minutes} = $dtstart->minute unless exists $by{minutes};
115 10 100       65 $by{hours} = $args{byhour} if exists $args{byhour};
116 10 100       57 $by{hours} = $dtstart->hour unless exists $by{hours};
117             delete $$argsref{$_}
118 10         91 for qw( interval bysecond byminute byhour );
119             # TODO: (maybe) - same thing if byweekno exists
120 10 100 66     55 $$argsref{bymonthday} = [ 1 .. 31 ]
121             if exists $args{bymonth} && ! exists $args{bymonthday};
122 10         96 return DateTime::Event::Recurrence->daily( %by );
123             }
124              
125             sub _weekly_recurrence {
126 10     10   20 my ($dtstart, $argsref) = @_;
127 10         19 my %by;
128 10         48 my %args = %$argsref;
129 10 100       59 $by{interval} = $args{interval} if exists $args{interval};
130 10         26 $by{start} = $dtstart;
131 10 50       44 $by{seconds} = $args{bysecond} if exists $args{bysecond};
132 10 50       73 $by{seconds} = $dtstart->second unless exists $by{seconds};
133 10 100       88 $by{minutes} = $args{byminute} if exists $args{byminute};
134 10 100       63 $by{minutes} = $dtstart->minute unless exists $by{minutes};
135 10 100       79 $by{hours} = $args{byhour} if exists $args{byhour};
136 10 100       59 $by{hours} = $dtstart->hour unless exists $by{hours};
137              
138 10 100       89 $by{week_start_day} = $args{wkst} ?
139             $args{wkst} : 'mo';
140              
141             # -1fr works too
142 20         39 $by{days} = exists $args{byday} ?
143 20         231 [ map { $_ =~ s/[\-\+\d]+//; $weekdays{$_} }
  7         23  
144 10 100       45 @{$args{byday}}
145             ] :
146             $dtstart->day_of_week ;
147             # warn "weekly:"._param_str(%by);
148              
149             delete $$argsref{$_}
150 10         79 for qw( interval bysecond byminute byhour byday );
151 10         118 return DateTime::Event::Recurrence->weekly( %by );
152             }
153              
154             sub _monthly_recurrence {
155 20     20   40 my ($dtstart, $argsref) = @_;
156 20         51 my %by;
157 20         87 my %args = %$argsref;
158 20 50       111 $by{interval} = $args{interval} if exists $args{interval};
159 20         46 $by{start} = $dtstart;
160 20 50       70 $by{seconds} = $args{bysecond} if exists $args{bysecond};
161 20 50       126 $by{seconds} = $dtstart->second unless exists $by{seconds};
162 20 50       179 $by{minutes} = $args{byminute} if exists $args{byminute};
163 20 50       119 $by{minutes} = $dtstart->minute unless exists $by{minutes};
164 20 100       150 $by{hours} = $args{byhour} if exists $args{byhour};
165 20 100       101 $by{hours} = $dtstart->hour unless exists $by{hours};
166              
167 20 50       154 $by{week_start_day} = $args{wkst} ?
168             $args{wkst} : '1mo';
169              
170 20 100       94 if ( exists $args{bymonthday} )
    100          
171             {
172 8         21 $by{days} = $args{bymonthday};
173             }
174             elsif ( exists $args{byday} )
175             {
176 11         62 $by{days} = [ 1 .. 31 ];
177             }
178             else
179             {
180 1 50       9 $by{days} = $dtstart->day unless exists $by{days};
181             }
182              
183 20         38 my $set_byday;
184 20 100       79 if ( exists $args{byday} )
185             {
186 13         22 my $freq = 'monthly';
187              
188 13         18 my %by;
189 13 50       48 $by{seconds} = $args{bysecond} if exists $args{bysecond};
190 13 50       63 $by{seconds} = $dtstart->second unless exists $by{seconds};
191 13 50       148 $by{minutes} = $args{byminute} if exists $args{byminute};
192 13 50       61 $by{minutes} = $dtstart->minute unless exists $by{minutes};
193 13 100       171 $by{hours} = $args{byhour} if exists $args{byhour};
194 13 100       61 $by{hours} = $dtstart->hour unless exists $by{hours};
195              
196             # process byday = "1FR" and "FR"
197 13         101 $set_byday = _recur_1fr(
198             %by, byday => $args{byday}, freq => $freq );
199             delete $$argsref{$_}
200 13         76 for qw( byday );
201             }
202              
203             delete $$argsref{$_}
204 20         134 for qw( interval bysecond byminute byhour bymonthday );
205 20 100       127 return DateTime::Event::Recurrence->monthly( %by )->intersection( $set_byday
206             ) if $set_byday;
207 7         53 return DateTime::Event::Recurrence->monthly( %by );
208             }
209              
210             sub _yearly_recurrence {
211 24     24   43 my ($dtstart, $argsref) = @_;
212 24         36 my %by;
213 24         139 my %args = %$argsref;
214 24 100       124 $by{interval} = $args{interval} if exists $args{interval};
215 24         76 $by{start} = $dtstart;
216 24 50       93 $by{seconds} = $args{bysecond} if exists $args{bysecond};
217 24 50       2253 $by{seconds} = $dtstart->second unless exists $by{seconds};
218 24 100       1568 $by{minutes} = $args{byminute} if exists $args{byminute};
219 24 100       366 $by{minutes} = $dtstart->minute unless exists $by{minutes};
220 24 100       201 $by{hours} = $args{byhour} if exists $args{byhour};
221 24 100       133 $by{hours} = $dtstart->hour unless exists $by{hours};
222              
223 24 50       277 $by{week_start_day} = $args{wkst} ?
224             $args{wkst} : 'mo';
225             # warn "wkst $by{week_start_day}";
226              
227 24 100       123 if ( exists $args{bymonth} )
    100          
    100          
    50          
228             {
229 16         40 $by{months} = $args{bymonth};
230 16         41 delete $$argsref{bymonth};
231              
232 16 100       73 $by{days} = $args{bymonthday} if exists $args{bymonthday};
233 16 100 100     161 $by{days} = [ 1 .. 31 ]
234             if ! exists $by{days} &&
235             exists $args{byday};
236 16 100       66 $by{days} = $dtstart->day unless exists $by{days};
237 16         61 delete $$argsref{bymonthday};
238             }
239             elsif ( exists $args{byweekno} )
240             {
241 3         9 $by{weeks} = $args{byweekno};
242 3         9 delete $$argsref{byweekno};
243              
244 3 100       14 $by{days} = $args{byday} if exists $args{byday};
245 3 100       16 $by{days} = $dtstart->day_of_week unless exists $by{days};
246 3         14 delete $$argsref{byday};
247             }
248             elsif ( exists $args{byyearday} )
249             {
250 1         7 $by{days} = $args{byyearday};
251 1         5 delete $$argsref{byyearday};
252             }
253             elsif ( exists $args{byday} )
254             {
255 4         22 $by{months} = [ 1 .. 12 ];
256              
257 4 50       19 $by{days} = $args{bymonthday} if exists $args{bymonthday};
258 4 50       31 $by{days} = [ 1 .. 31 ]
259             if ! exists $by{days};
260 4 50       18 $by{days} = $dtstart->day unless exists $by{days};
261 4         11 delete $$argsref{bymonthday};
262             }
263             else
264             {
265 0         0 $by{months} = $dtstart->month;
266              
267 0 0       0 $by{days} = $args{bymonthday} if exists $args{bymonthday};
268 0 0       0 $by{days} = $dtstart->day unless exists $by{days};
269 0         0 delete $$argsref{bymonthday};
270             }
271              
272 24         55 my $set_byday;
273 24 100       108 if ( exists $args{byday} )
274             {
275 14         103 my $freq = 'yearly';
276 14 100       54 $freq = 'monthly' if exists $args{bymonth};
277              
278 14         22 my %by;
279 14 50       58 $by{seconds} = $args{bysecond} if exists $args{bysecond};
280 14 50       79 $by{seconds} = $dtstart->second unless exists $by{seconds};
281 14 100       110 $by{minutes} = $args{byminute} if exists $args{byminute};
282 14 100       70 $by{minutes} = $dtstart->minute unless exists $by{minutes};
283 14 100       113 $by{hours} = $args{byhour} if exists $args{byhour};
284 14 100       63 $by{hours} = $dtstart->hour unless exists $by{hours};
285              
286             # process byday = "1FR" and "FR"
287 14         158 $set_byday = _recur_1fr(
288             %by, byday => $args{byday}, freq => $freq );
289             delete $$argsref{$_}
290 14         80 for qw( byday );
291             }
292              
293             delete $$argsref{$_}
294 24         361 for qw( interval byday bysecond byminute byhour );
295 24 100       165 return DateTime::Event::Recurrence->yearly( %by )->intersection( $set_byday ) if $set_byday;
296 10         83 return DateTime::Event::Recurrence->yearly( %by );
297             }
298              
299             # recurrence constructor for '1FR' specification
300              
301             sub _recur_1fr {
302             # ( freq , interval, dtstart, byday[ week_count . week_day ] )
303 27     27   143 my %args = @_;
304 27         49 my $base_set;
305             my %days;
306 0         0 my $base_duration;
307 0         0 my @days_no_index;
308              
309             # parse byday
310 27 100       127 $args{byday} = [ $args{byday} ] unless ref $args{byday} eq 'ARRAY';
311 27         46 for ( @{$args{byday}} )
  27         86  
312             {
313 55         364 my ( $count, $day_name ) = $_ =~ /(.*)(\w\w)/;
314 55         466 my $week_day = $weekdays{ $day_name };
315 55 50       125 die "invalid week day ($day_name)" unless $week_day;
316 55 100       115 if ( $count )
317             {
318 21         34 push @{$days{$day_name}}, $count;
  21         92  
319             }
320             else
321             {
322             # die "week count ($count) can't be zero" unless $count;
323 34         89 push @days_no_index, $week_day;
324             }
325             }
326 27         69 delete $args{byday};
327              
328 27         46 my $result;
329 27 100       77 if ( @days_no_index )
330             {
331 16         69 my %_args = %args;
332 16         43 $_args{days} = \@days_no_index;
333 16         32 delete $_args{freq};
334 16         121 $result = DateTime::Event::Recurrence->weekly( %_args );
335             }
336 27         11645 for ( keys %days )
337             {
338 18         76 my %_args = %args;
339 18         46 $_args{weeks} = $days{$_};
340 18         44 $_args{week_start_day} = '1'.$_;
341             # warn "creating set with $_ "._param_str( %_args );
342              
343 18 100       147 if ( $_args{freq} eq 'monthly' ) {
    50          
344 12         21 $base_duration = 'months';
345 12         27 delete $_args{freq};
346             # warn "creating base set with "._param_str( %args );
347 12         995 $base_set = DateTime::Event::Recurrence->monthly( %_args )
348             }
349             elsif ( $_args{freq} eq 'yearly' ) {
350 6         10 $base_duration = 'years';
351 6         10 delete $_args{freq};
352 6         38 $base_set = DateTime::Event::Recurrence->yearly( %_args )
353             }
354             else {
355 0         0 die "invalid freq ($_args{freq})";
356             }
357              
358 18 100       11074 $result = $result ?
359             $result->union( $base_set ) :
360             $base_set;
361             }
362 27         1791 return $result;
363             }
364              
365             # bysetpos constructor
366              
367             sub _recur_bysetpos {
368             # ( freq , interval, bysetpos, recurrence )
369 3     3   20 my %args = @_;
370             # my $names = $freqs{ $args{freq} }{names};
371             # my $name = $freqs{ $args{freq} }{name};
372 7     7   55 no strict "refs";
  7         17  
  7         13923  
373              
374 3         8 my $freq = $args{freq};
375              
376 3         15 my $base_set = DateTime::Event::Recurrence->$freq();
377              
378             # die "invalid freq parameter ($args{freq})"
379             # unless exists $DateTime::Event::Recurrence::truncate_interval{ $names };
380             #my $truncate_interval_sub =
381             # $DateTime::Event::Recurrence::truncate_interval{ $names };
382             #my $next_unit_sub =
383             # $DateTime::Event::Recurrence::next_unit{ $names };
384             #my $previous_unit_sub =
385             # $DateTime::Event::Recurrence::previous_unit{ $names };
386              
387 3 100       743 $args{bysetpos} = [ $args{bysetpos} ]
388             unless ref( $args{bysetpos} );
389             # die "invalid bysetpos parameter [@{$args{bysetpos}}]"
390             # unless @{$args{bysetpos}};
391             # print STDERR "bysetpos: [@{$args{bysetpos}}]\n";
392 3 100       7 for ( @{$args{bysetpos}} ) { $_-- if $_ > 0 }
  3         8  
  4         16  
393              
394             return DateTime::Set->from_recurrence (
395             next =>
396             sub {
397              
398 41 100   41   27634 return $_[0] if $_[0]->is_infinite;
399              
400             ## return undef unless defined $_[0];
401 37         288 my $self = $_[0]->clone;
402             # warn "bysetpos: next of ".$_[0]->datetime;
403             # print STDERR " list [@{$args{bysetpos}}] \n";
404             # print STDERR " previous: ".$base_set->current( $_[0] )->datetime."\n";
405 37         550 my $start = $base_set->current( $_[0] );
406 37         84280 while(1) {
407 62         270 my $end = $base_set->next( $start->clone );
408 62 100       66542 if ( $#{$args{bysetpos}} == 0 ) {
  62         346  
409             # optimize by using 'next' instead of 'intersection'
410              
411 27         71 my $pos = $args{bysetpos}[0];
412 27 100       73 if ( $pos >= 0 ) {
413 12         39 my $next = $start->clone;
414 12         165 $next->subtract( nanoseconds => 1 );
415 12         8091 while ( $pos-- >= 0 ) {
416             # print STDERR " next: $pos ".$next->datetime."\n";
417 36         503618 $next = $args{recurrence}->next( $next )
418             }
419 12 100       235651 return $next if $next > $self;
420             }
421             else {
422 15         51 my $next = $end->clone;
423 15         174 while ( $pos++ < 0 ) {
424             # print STDERR " previous: $pos ".$next->datetime."\n";
425 30         425287 $next = $args{recurrence}->previous( $next )
426             }
427 15 100       298131 return $next if $next > $self;
428             }
429              
430             }
431             else {
432             # print STDERR " base: ".$start->datetime." ".$end->datetime."\n";
433 35         229 my $span = DateTime::Span->from_datetimes(
434             start => $start,
435             before => $end );
436             # print STDERR " done span\n";
437 35         49756 my $subset = $args{recurrence}->intersection( $span );
438 35         2732864 my @list = $subset->as_list;
439             # print STDERR " got list ".join(",", map{$_->datetime}@list)."\n";
440              
441             # select
442 35         477184 my @l = @list[ @{$args{bysetpos}} ];
  35         198  
443 35         101 @l = grep { defined $_ } @l;
  70         226  
444 35         250 @list = sort { $a <=> $b } @l;
  35         164  
445             ## @list = sort { $a <=> $b } @list[ @{$args{bysetpos}} ];
446              
447             # print STDERR " selected [@{$args{bysetpos}}]".join(",", map{$_->datetime}@list)."\n";
448 35         2391 for ( @list ) {
449             # print STDERR " choose: ".$_->datetime."\n" if $_ > $self;
450 58 100       1565 return $_ if $_ > $self;
451             }
452             }
453 25         2065 $start = $end;
454             } # /while
455             },
456             previous =>
457             sub {
458              
459 12 100   12   1830 return $_[0] if $_[0]->is_infinite;
460              
461 7         52 my $self = $_[0]->clone;
462             # warn "bysetpos: previous of ".$_[0]->datetime;
463             # print STDERR " previous: ".$base_set->current( $_[0] )->datetime."\n";
464 7         107 my $start = $base_set->current( $_[0] );
465 7         14990 my $end = $base_set->next( $start->clone );
466 7         6847 my $count = 10;
467 7         14 while(1) {
468             # print STDERR " base: ".$start->datetime." ".$end->datetime."\n";
469 12         6410 my $span = DateTime::Span->from_datetimes(
470             start => $start,
471             before => $end );
472             # print STDERR " done span\n";
473 12         12294 my $subset = $args{recurrence}->intersection( $span );
474 12         2949325 my @list = $subset->as_list;
475             # print STDERR " got list ".join(",", map{$_->datetime}@list)."\n";
476              
477             # select
478 12         451662 my @l = @list[ @{$args{bysetpos}} ];
  12         64  
479 12         33 @l = grep { defined $_ } @l;
  16         61  
480 12         88 @list = sort { $b <=> $a } @l;
  4         18  
481             ## @list = sort { $a <=> $b } @list[ @{$args{bysetpos}} ];
482              
483             # print STDERR " selected [@{$args{bysetpos}}]".join(",", map{$_->datetime}@list)."\n";
484 12         317 for ( @list ) {
485 15 100       259 return $_ if $_ < $self;
486             }
487 5 50       309 return undef unless $count--;
488 5         11 $end = $start;
489 5         23 $start = $base_set->previous( $start );
490             } # /while
491             }
492 3         41 );
493             }
494              
495             # map frequencies to recurrence constructors
496             {
497             my %frequencies = (
498             secondly => \&_secondly_recurrence,
499             minutely => \&_minutely_recurrence,
500             hourly => \&_hourly_recurrence,
501             daily => \&_daily_recurrence,
502             monthly => \&_monthly_recurrence,
503             weekly => \&_weekly_recurrence,
504             yearly => \&_yearly_recurrence,
505             );
506              
507             sub _recur_by_freq {
508 67     67   138 my ($freq,$dtstart,$args) = @_;
509              
510 67 50       464 return $frequencies{$freq}->($dtstart,$args)
511             if exists $frequencies{$freq};
512              
513 0         0 return undef;
514             }
515             }
516              
517             # main recurrence constructor
518              
519             sub recur {
520 87     87 1 57369112 my $class = shift;
521 87         2775 my %args = @_;
522 87         313 my %args_backup = @_;
523              
524 87 100       432 if ( exists $args{count} )
525             {
526             # count
527 20         43 my $n = $args{count};
528 20         54 delete $args{count};
529 20         132 my $count_inf = $class->recur( %args )->{set}
530             ->select( count => $n );
531 20         6030993 return bless { set => $count_inf }, 'DateTime::Set';
532             }
533              
534             # warn "recur:"._param_str(%args);
535              
536             # stringify the argument list - will be used by format_recurrence !
537 67         265 my %tmp_args = @_;
538 67         155 delete $tmp_args{dtstart};
539 67         341 delete $tmp_args{dtend};
540 67         581 my $recur_str = _param_str(%tmp_args);
541              
542             # dtstart / dtend / until
543 67 100       691 my $span =
544             exists $args{dtstart} ?
545             DateTime::Span->from_datetimes( start => $args{dtstart} ) :
546             DateTime::Set->empty_set->complement;
547              
548 67 100       17661 $span = $span->complement(
549             DateTime::Span->from_datetimes( after => delete $args{dtend} )
550             ) if exists $args{dtend};
551              
552 67 100       11599 $span = $span->complement(
553             DateTime::Span->from_datetimes( after => delete $args{until} )
554             ) if exists $args{until};
555             # warn 'SPAN '. $span->{set};
556              
557 67 100       8355 $args{interval} = 1 unless $args{interval};
558              
559             # setup the "default time"
560 67 100       290 my $dtstart = exists $args{dtstart} ?
561             delete $args{dtstart} :
562             DateTime->new( year => 2000, month => 1, day => 1 );
563             # warn 'DTSTART '. $dtstart->datetime;
564              
565             # rewrite: daily-bymonth to yearly-bymonth-bymonthday[1..31]
566 67 100       749 if ( $args{freq} eq 'daily' ) {
567 8 100 100     52 if ( exists $args{bymonth} &&
568             $args{interval} == 1 )
569             {
570 1         3 $args{freq} = 'yearly';
571 1 50       7 $args{bymonthday} = [ 1 .. 31 ] unless exists $args{bymonthday};
572             # warn "rewrite recur:"._param_str(%args);
573             }
574             }
575              
576 67         123 my $base_set;
577             my %by;
578              
579 67         319 $base_set = _recur_by_freq($args{freq},$dtstart,\%args);
580 67 50       61034 unless (defined $base_set) {
581 0         0 die "invalid freq ($args{freq})";
582             }
583              
584 67         170 delete $args{wkst}; # TODO: wkst
585              
586             # warn "\ncomplex recur:"._param_str(%args);
587              
588 67         146 %by = ();
589 67         125 my $has_day = 0;
590              
591 67         100 my $by_year_day;
592 67 50       254 if ( exists $args{byyearday} )
593             {
594 0         0 $by_year_day = _yearly_recurrence($dtstart, \%args);
595             }
596              
597 67         97 my $by_month_day;
598 67 100 100     521 if ( exists $args{bymonthday} ||
599             exists $args{bymonth} )
600             {
601 4         17 my %by = %args;
602 4 100       18 $by{byhour} = $args_backup{byhour} if $args_backup{byhour};
603 4 100       17 $by{byhour} = [ 0 .. 23 ] if $args{freq} eq 'hourly';
604 4 50       12 $by{byminute} = $args_backup{byminute} if $args_backup{byminute};
605 4 100       32 $by{byminute} = [ 0 .. 59 ] if $args{freq} eq 'minutely';
606 4 50       18 $by{bysecond} = $args_backup{bysecond} if $args_backup{bysecond};
607 4 50       15 $by{bysecond} = [ 0 .. 59 ] if $args{freq} eq 'secondly';
608 4         15 $by_month_day = _yearly_recurrence($dtstart, \%by);
609 4         3442 delete $args{bymonthday};
610 4         11 delete $args{bymonth};
611             }
612              
613 67         97 my $by_week_day;
614             # TODO: byweekno without byday
615 67 100 66     3883 if ( exists $args{byday} ||
616             exists $args{byweekno} )
617             {
618 1         4 my %by = %args;
619 1 50       5 $by{byhour} = $args_backup{byhour} if $args_backup{byhour};
620 1 50       4 $by{byhour} = [ 0 .. 23 ] if $args{freq} eq 'hourly';
621 1 50       3 $by{byminute} = $args_backup{byminute} if $args_backup{byminute};
622 1 50       8 $by{byminute} = [ 0 .. 59 ] if $args{freq} eq 'minutely';
623 1 50       4 $by{bysecond} = $args_backup{bysecond} if $args_backup{bysecond};
624 1 50       4 $by{bysecond} = [ 0 .. 59 ] if $args{freq} eq 'secondly';
625 1         4 $by_week_day = _weekly_recurrence($dtstart, \%by);
626 1         822 delete $args{byday};
627 1         2 delete $args{byweekno};
628             }
629              
630 67         112 my $by_hour;
631 67 100       252 if ( exists $args{byhour} )
632             {
633 3         11 my %by = %args;
634 3 50       13 $by{byminute} = $args_backup{byminute} if $args_backup{byminute};
635 3 50       45 $by{byminute} = [ 0 .. 59 ] if $args{freq} eq 'minutely';
636 3 50       12 $by{bysecond} = $args_backup{bysecond} if $args_backup{bysecond};
637 3 50       46 $by{bysecond} = [ 0 .. 59 ] if $args{freq} eq 'secondly';
638 3         14 $by_hour = _daily_recurrence($dtstart, \%by);
639 3         2328 delete $args{byhour};
640             }
641              
642             # join the rules together
643              
644 67 50 33     423 $base_set = $base_set && $by_year_day ?
    50          
645             $base_set->intersection( $by_year_day ) :
646             ( $base_set ? $base_set : $by_year_day );
647 67 50 66     1521 $base_set = $base_set && $by_month_day ?
    100          
648             $base_set->intersection( $by_month_day ) :
649             ( $base_set ? $base_set : $by_month_day );
650 67 50 66     1149 $base_set = $base_set && $by_week_day ?
    100          
651             $base_set->intersection( $by_week_day ) :
652             ( $base_set ? $base_set : $by_week_day );
653 67 50 66     489 $base_set = $base_set && $by_hour ?
    100          
654             $base_set->intersection( $by_hour ) :
655             ( $base_set ? $base_set : $by_hour );
656              
657             # TODO:
658             # wkst
659             # bysetpos
660              
661 67 100       749 if ( exists $args{bysetpos} ) {
662 3         24 $base_set = _recur_bysetpos (
663             freq => $args{freq},
664             interval => $args{interval},
665             bysetpos => $args{bysetpos},
666             recurrence => $base_set );
667 3         299 delete $args{bysetpos};
668             }
669              
670 67 50       496 $base_set = $base_set->intersection( $span )
671             if $span;
672              
673             # check for nonprocessed arguments
674 67         13246392 delete $args{freq};
675 67         185 my @args = %args;
676 67 50       245 die "these arguments are not implemented: "._param_str(%args) if @args;
677              
678 67         208 bless $base_set, 'DateTime::Set::ICal';
679 67         405 $base_set->set_ical( include => [ uc('rrule:'.$recur_str) ] );
680              
681 67         1286 return $base_set;
682             }
683              
684             __END__