File Coverage

blib/lib/DateTime/Event/ICal.pm
Criterion Covered Total %
statement 353 365 96.7
branch 218 282 77.3
condition 18 27 66.6
subroutine 26 26 100.0
pod 1 1 100.0
total 616 701 87.8


line stmt bran cond sub pod time code
1             package DateTime::Event::ICal;
2              
3 7     7   1038964 use strict;
  7         16  
  7         225  
4             require Exporter;
5 7     7   36 use Carp;
  7         13  
  7         507  
6 7     7   1290 use DateTime;
  7         137090  
  7         152  
7 7     7   4871 use DateTime::Set;
  7         289317  
  7         215  
8 7     7   74 use DateTime::Span;
  7         11  
  7         174  
9 7     7   37 use DateTime::SpanSet;
  7         16  
  7         185  
10 7     7   6255 use DateTime::Event::Recurrence 0.11;
  7         42794  
  7         248  
11 7     7   56 use Params::Validate qw(:all);
  7         14  
  7         1184  
12 7     7   37 use vars qw( $VERSION @ISA );
  7         14  
  7         525  
13             @ISA = qw( Exporter );
14             $VERSION = '0.13';
15              
16 7     7   105 use constant INFINITY => 100 ** 100 ** 100 ;
  7         16  
  7         426  
17 7     7   35 use constant NEG_INFINITY => -1 * (100 ** 100 ** 100);
  7         16  
  7         16946  
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   259 my %param = @_;
36 67         142 my @str;
37 67         619 for ( qw( freq interval count ),
38             qw( byyear bymonth byday ),
39             sort keys %param )
40             {
41 591 100       1378 next unless exists $param{$_};
42 189 100       1448 if ( ref( $param{$_} ) eq 'ARRAY' ) {
    100          
    50          
43 65         217 push @str, "$_=". join( ',', @{$param{$_}} )
  65         268  
44             }
45             elsif ( UNIVERSAL::can( $param{$_}, 'datetime' ) ) {
46 8 50       28 if ( $DateTime::Format::ICal::VERSION ) {
47 0         0 push @str, "$_=" . DateTime::Format::ICal->format_datetime( $param{$_} );
48             }
49             else {
50 8         46 push @str, "$_=". $param{$_}->datetime
51             }
52             }
53             elsif ( defined $param{$_} ) {
54 116         400 push @str, "$_=". $param{$_}
55             }
56             else {
57 0         0 push @str, "$_=undef"
58             }
59 189         578 delete $param{$_};
60             }
61 67         387 return join(';', @str);
62             }
63              
64             # recurrence constructors
65              
66             sub _secondly_recurrence {
67 1     1   3 my ($dtstart, $argsref) = @_;
68 1         3 my %by;
69 1         7 my %args = %$argsref;
70 1 50       8 $by{interval} = $args{interval} if exists $args{interval};
71 1         4 $by{start} = $dtstart;
72             delete $$argsref{$_}
73 1         5 for qw( interval );
74 1         10 return DateTime::Event::Recurrence->secondly( %by );
75             }
76              
77             sub _minutely_recurrence {
78 7     7   15 my ($dtstart, $argsref) = @_;
79 7         25 my %by;
80 7         32 my %args = %$argsref;
81 7 50       39 $by{interval} = $args{interval} if exists $args{interval};
82 7         30 $by{start} = $dtstart;
83 7 50       25 $by{seconds} = $args{bysecond} if exists $args{bysecond};
84 7 50       44 $by{seconds} = $dtstart->second unless exists $by{seconds};
85             delete $$argsref{$_}
86 7         62 for qw( interval bysecond );
87 7         63 return DateTime::Event::Recurrence->minutely( %by );
88             }
89              
90             sub _hourly_recurrence {
91 3     3   7 my ($dtstart, $argsref) = @_;
92 3         8 my %by;
93 3         15 my %args = %$argsref;
94 3 50       25 $by{interval} = $args{interval} if exists $args{interval};
95 3         10 $by{start} = $dtstart;
96 3 100       16 $by{seconds} = $args{bysecond} if exists $args{bysecond};
97 3 100       19 $by{seconds} = $dtstart->second unless exists $by{seconds};
98 3 50       22 $by{minutes} = $args{byminute} if exists $args{byminute};
99 3 50       20 $by{minutes} = $dtstart->minute unless exists $by{minutes};
100             delete $$argsref{$_}
101 3         29 for qw( interval byminute bysecond );
102 3         23 return DateTime::Event::Recurrence->hourly( %by );
103             }
104              
105             sub _daily_recurrence {
106 10     10   19 my ($dtstart, $argsref) = @_;
107 10         15 my %by;
108 10         48 my %args = %$argsref;
109 10 100       45 $by{interval} = $args{interval} if exists $args{interval};
110 10         49 $by{start} = $dtstart;
111 10 50       29 $by{seconds} = $args{bysecond} if exists $args{bysecond};
112 10 50       59 $by{seconds} = $dtstart->second unless exists $by{seconds};
113 10 100       81 $by{minutes} = $args{byminute} if exists $args{byminute};
114 10 100       52 $by{minutes} = $dtstart->minute unless exists $by{minutes};
115 10 100       80 $by{hours} = $args{byhour} if exists $args{byhour};
116 10 100       47 $by{hours} = $dtstart->hour unless exists $by{hours};
117             delete $$argsref{$_}
118 10         77 for qw( interval bysecond byminute byhour );
119             # TODO: (maybe) - same thing if byweekno exists
120             $$argsref{bymonthday} = [ 1 .. 31 ]
121 10 100 66     42 if exists $args{bymonth} && ! exists $args{bymonthday};
122 10         78 return DateTime::Event::Recurrence->daily( %by );
123             }
124              
125             sub _weekly_recurrence {
126 10     10   18 my ($dtstart, $argsref) = @_;
127 10         19 my %by;
128 10         54 my %args = %$argsref;
129 10 100       62 $by{interval} = $args{interval} if exists $args{interval};
130 10         28 $by{start} = $dtstart;
131 10 50       50 $by{seconds} = $args{bysecond} if exists $args{bysecond};
132 10 50       68 $by{seconds} = $dtstart->second unless exists $by{seconds};
133 10 100       95 $by{minutes} = $args{byminute} if exists $args{byminute};
134 10 100       57 $by{minutes} = $dtstart->minute unless exists $by{minutes};
135 10 100       74 $by{hours} = $args{byhour} if exists $args{byhour};
136 10 100       54 $by{hours} = $dtstart->hour unless exists $by{hours};
137              
138             $by{week_start_day} = $args{wkst} ?
139 10 100       96 $args{wkst} : 'mo';
140              
141             # -1fr works too
142             $by{days} = exists $args{byday} ?
143 20         44 [ map { $_ =~ s/[\-\+\d]+//; $weekdays{$_} }
  20         58  
144 10 100       45 @{$args{byday}}
  7         18  
145             ] :
146             $dtstart->day_of_week ;
147             # warn "weekly:"._param_str(%by);
148              
149             delete $$argsref{$_}
150 10         78 for qw( interval bysecond byminute byhour byday );
151 10         126 return DateTime::Event::Recurrence->weekly( %by );
152             }
153              
154             sub _monthly_recurrence {
155 20     20   44 my ($dtstart, $argsref) = @_;
156 20         37 my %by;
157 20         95 my %args = %$argsref;
158 20 50       117 $by{interval} = $args{interval} if exists $args{interval};
159 20         49 $by{start} = $dtstart;
160 20 50       73 $by{seconds} = $args{bysecond} if exists $args{bysecond};
161 20 50       130 $by{seconds} = $dtstart->second unless exists $by{seconds};
162 20 50       180 $by{minutes} = $args{byminute} if exists $args{byminute};
163 20 50       105 $by{minutes} = $dtstart->minute unless exists $by{minutes};
164 20 100       145 $by{hours} = $args{byhour} if exists $args{byhour};
165 20 100       102 $by{hours} = $dtstart->hour unless exists $by{hours};
166              
167             $by{week_start_day} = $args{wkst} ?
168 20 50       150 $args{wkst} : '1mo';
169              
170 20 100       74 if ( exists $args{bymonthday} )
    100          
171             {
172 8         25 $by{days} = $args{bymonthday};
173             }
174             elsif ( exists $args{byday} )
175             {
176 11         58 $by{days} = [ 1 .. 31 ];
177             }
178             else
179             {
180 1 50       7 $by{days} = $dtstart->day unless exists $by{days};
181             }
182              
183 20         43 my $set_byday;
184 20 100       70 if ( exists $args{byday} )
185             {
186 13         31 my $freq = 'monthly';
187              
188 13         23 my %by;
189 13 50       41 $by{seconds} = $args{bysecond} if exists $args{bysecond};
190 13 50       62 $by{seconds} = $dtstart->second unless exists $by{seconds};
191 13 50       120 $by{minutes} = $args{byminute} if exists $args{byminute};
192 13 50       62 $by{minutes} = $dtstart->minute unless exists $by{minutes};
193 13 100       85 $by{hours} = $args{byhour} if exists $args{byhour};
194 13 100       49 $by{hours} = $dtstart->hour unless exists $by{hours};
195              
196             # process byday = "1FR" and "FR"
197             $set_byday = _recur_1fr(
198 13         96 %by, byday => $args{byday}, freq => $freq );
199             delete $$argsref{$_}
200 13         66 for qw( byday );
201             }
202              
203             delete $$argsref{$_}
204 20         110 for qw( interval bysecond byminute byhour bymonthday );
205 20 100       119 return DateTime::Event::Recurrence->monthly( %by )->intersection( $set_byday
206             ) if $set_byday;
207 7         50 return DateTime::Event::Recurrence->monthly( %by );
208             }
209              
210             sub _yearly_recurrence {
211 24     24   52 my ($dtstart, $argsref) = @_;
212 24         37 my %by;
213 24         118 my %args = %$argsref;
214 24 100       122 $by{interval} = $args{interval} if exists $args{interval};
215 24         52 $by{start} = $dtstart;
216 24 50       80 $by{seconds} = $args{bysecond} if exists $args{bysecond};
217 24 50       124 $by{seconds} = $dtstart->second unless exists $by{seconds};
218 24 100       194 $by{minutes} = $args{byminute} if exists $args{byminute};
219 24 100       113 $by{minutes} = $dtstart->minute unless exists $by{minutes};
220 24 100       169 $by{hours} = $args{byhour} if exists $args{byhour};
221 24 100       102 $by{hours} = $dtstart->hour unless exists $by{hours};
222              
223             $by{week_start_day} = $args{wkst} ?
224 24 50       153 $args{wkst} : 'mo';
225             # warn "wkst $by{week_start_day}";
226              
227 24 100       542 if ( exists $args{bymonth} )
    100          
    100          
    50          
228             {
229 16         43 $by{months} = $args{bymonth};
230 16         31 delete $$argsref{bymonth};
231              
232 16 100       65 $by{days} = $args{bymonthday} if exists $args{bymonthday};
233             $by{days} = [ 1 .. 31 ]
234             if ! exists $by{days} &&
235 16 100 66     105 exists $args{byday};
236 16 100       53 $by{days} = $dtstart->day unless exists $by{days};
237 16         39 delete $$argsref{bymonthday};
238             }
239             elsif ( exists $args{byweekno} )
240             {
241 3         12 $by{weeks} = $args{byweekno};
242 3         6 delete $$argsref{byweekno};
243              
244 3 100       23 $by{days} = $args{byday} if exists $args{byday};
245 3 100       18 $by{days} = $dtstart->day_of_week unless exists $by{days};
246 3         12 delete $$argsref{byday};
247             }
248             elsif ( exists $args{byyearday} )
249             {
250 1         4 $by{days} = $args{byyearday};
251 1         3 delete $$argsref{byyearday};
252             }
253             elsif ( exists $args{byday} )
254             {
255 4         20 $by{months} = [ 1 .. 12 ];
256              
257 4 50       17 $by{days} = $args{bymonthday} if exists $args{bymonthday};
258             $by{days} = [ 1 .. 31 ]
259 4 50       25 if ! exists $by{days};
260 4 50       15 $by{days} = $dtstart->day unless exists $by{days};
261 4         9 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         38 my $set_byday;
273 24 100       84 if ( exists $args{byday} )
274             {
275 14         34 my $freq = 'yearly';
276 14 100       48 $freq = 'monthly' if exists $args{bymonth};
277              
278 14         23 my %by;
279 14 50       42 $by{seconds} = $args{bysecond} if exists $args{bysecond};
280 14 50       73 $by{seconds} = $dtstart->second unless exists $by{seconds};
281 14 100       98 $by{minutes} = $args{byminute} if exists $args{byminute};
282 14 100       62 $by{minutes} = $dtstart->minute unless exists $by{minutes};
283 14 100       91 $by{hours} = $args{byhour} if exists $args{byhour};
284 14 100       70 $by{hours} = $dtstart->hour unless exists $by{hours};
285              
286             # process byday = "1FR" and "FR"
287             $set_byday = _recur_1fr(
288 14         97 %by, byday => $args{byday}, freq => $freq );
289             delete $$argsref{$_}
290 14         72 for qw( byday );
291             }
292              
293             delete $$argsref{$_}
294 24         132 for qw( interval byday bysecond byminute byhour );
295 24 100       123 return DateTime::Event::Recurrence->yearly( %by )->intersection( $set_byday ) if $set_byday;
296 10         73 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   124 my %args = @_;
304 27         43 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       120 $args{byday} = [ $args{byday} ] unless ref $args{byday} eq 'ARRAY';
311 27         54 for ( @{$args{byday}} )
  27         89  
312             {
313 55         272 my ( $count, $day_name ) = $_ =~ /(.*)(\w\w)/;
314 55         147 my $week_day = $weekdays{ $day_name };
315 55 50       130 die "invalid week day ($day_name)" unless $week_day;
316 55 100       118 if ( $count )
317             {
318 21         27 push @{$days{$day_name}}, $count;
  21         80  
319             }
320             else
321             {
322             # die "week count ($count) can't be zero" unless $count;
323 34         72 push @days_no_index, $week_day;
324             }
325             }
326 27         88 delete $args{byday};
327              
328 27         42 my $result;
329 27 100       90 if ( @days_no_index )
330             {
331 16         59 my %_args = %args;
332 16         49 $_args{days} = \@days_no_index;
333 16         24 delete $_args{freq};
334 16         170 $result = DateTime::Event::Recurrence->weekly( %_args );
335             }
336 27         8414 for ( keys %days )
337             {
338 18         69 my %_args = %args;
339 18         48 $_args{weeks} = $days{$_};
340 18         53 $_args{week_start_day} = '1'.$_;
341             # warn "creating set with $_ "._param_str( %_args );
342              
343 18 100       62 if ( $_args{freq} eq 'monthly' ) {
    50          
344 12         22 $base_duration = 'months';
345 12         28 delete $_args{freq};
346             # warn "creating base set with "._param_str( %args );
347 12         90 $base_set = DateTime::Event::Recurrence->monthly( %_args )
348             }
349             elsif ( $_args{freq} eq 'yearly' ) {
350 6         11 $base_duration = 'years';
351 6         10 delete $_args{freq};
352 6         34 $base_set = DateTime::Event::Recurrence->yearly( %_args )
353             }
354             else {
355 0         0 die "invalid freq ($_args{freq})";
356             }
357              
358 18 100       8801 $result = $result ?
359             $result->union( $base_set ) :
360             $base_set;
361             }
362 27         1325 return $result;
363             }
364              
365             # bysetpos constructor
366              
367             sub _recur_bysetpos {
368             # ( freq , interval, bysetpos, recurrence )
369 3     3   16 my %args = @_;
370             # my $names = $freqs{ $args{freq} }{names};
371             # my $name = $freqs{ $args{freq} }{name};
372 7     7   42 no strict "refs";
  7         15  
  7         12341  
373              
374 3         7 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             $args{bysetpos} = [ $args{bysetpos} ]
388 3 100       657 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         9  
  4         14  
393              
394             return DateTime::Set->from_recurrence (
395             next =>
396             sub {
397              
398 41 100   41   24244 return $_[0] if $_[0]->is_infinite;
399              
400             ## return undef unless defined $_[0];
401 37         238 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         464 my $start = $base_set->current( $_[0] );
406 37         71356 while(1) {
407 62         202 my $end = $base_set->next( $start->clone );
408 62 100       58043 if ( $#{$args{bysetpos}} == 0 ) {
  62         260  
409             # optimize by using 'next' instead of 'intersection'
410              
411 27         60 my $pos = $args{bysetpos}[0];
412 27 100       76 if ( $pos >= 0 ) {
413 12         31 my $next = $start->clone;
414 12         151 $next->subtract( nanoseconds => 1 );
415 12         7465 while ( $pos-- >= 0 ) {
416             # print STDERR " next: $pos ".$next->datetime."\n";
417 36         440243 $next = $args{recurrence}->next( $next )
418             }
419 12 100       211461 return $next if $next > $self;
420             }
421             else {
422 15         37 my $next = $end->clone;
423 15         162 while ( $pos++ < 0 ) {
424             # print STDERR " previous: $pos ".$next->datetime."\n";
425 30         354643 $next = $args{recurrence}->previous( $next )
426             }
427 15 100       258097 return $next if $next > $self;
428             }
429              
430             }
431             else {
432             # print STDERR " base: ".$start->datetime." ".$end->datetime."\n";
433 35         155 my $span = DateTime::Span->from_datetimes(
434             start => $start,
435             before => $end );
436             # print STDERR " done span\n";
437 35         33840 my $subset = $args{recurrence}->intersection( $span );
438 35         2219380 my @list = $subset->as_list;
439             # print STDERR " got list ".join(",", map{$_->datetime}@list)."\n";
440              
441             # select
442 35         409121 my @l = @list[ @{$args{bysetpos}} ];
  35         188  
443 35         95 @l = grep { defined $_ } @l;
  70         189  
444 35         227 @list = sort { $a <=> $b } @l;
  35         133  
445             ## @list = sort { $a <=> $b } @list[ @{$args{bysetpos}} ];
446              
447             # print STDERR " selected [@{$args{bysetpos}}]".join(",", map{$_->datetime}@list)."\n";
448 35         2191 for ( @list ) {
449             # print STDERR " choose: ".$_->datetime."\n" if $_ > $self;
450 58 100       1448 return $_ if $_ > $self;
451             }
452             }
453 25         1805 $start = $end;
454             } # /while
455             },
456             previous =>
457             sub {
458              
459 12 100   12   1570 return $_[0] if $_[0]->is_infinite;
460              
461 7         41 my $self = $_[0]->clone;
462             # warn "bysetpos: previous of ".$_[0]->datetime;
463             # print STDERR " previous: ".$base_set->current( $_[0] )->datetime."\n";
464 7         91 my $start = $base_set->current( $_[0] );
465 7         13150 my $end = $base_set->next( $start->clone );
466 7         6310 my $count = 10;
467 7         16 while(1) {
468             # print STDERR " base: ".$start->datetime." ".$end->datetime."\n";
469 12         5898 my $span = DateTime::Span->from_datetimes(
470             start => $start,
471             before => $end );
472             # print STDERR " done span\n";
473 12         11650 my $subset = $args{recurrence}->intersection( $span );
474 12         2779688 my @list = $subset->as_list;
475             # print STDERR " got list ".join(",", map{$_->datetime}@list)."\n";
476              
477             # select
478 12         470417 my @l = @list[ @{$args{bysetpos}} ];
  12         56  
479 12         34 @l = grep { defined $_ } @l;
  16         56  
480 12         87 @list = sort { $b <=> $a } @l;
  4         15  
481             ## @list = sort { $a <=> $b } @list[ @{$args{bysetpos}} ];
482              
483             # print STDERR " selected [@{$args{bysetpos}}]".join(",", map{$_->datetime}@list)."\n";
484 12         270 for ( @list ) {
485 15 100       213 return $_ if $_ < $self;
486             }
487 5 50       314 return undef unless $count--;
488 5         13 $end = $start;
489 5         24 $start = $base_set->previous( $start );
490             } # /while
491             }
492 3         36 );
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   136 my ($freq,$dtstart,$args) = @_;
509              
510             return $frequencies{$freq}->($dtstart,$args)
511 67 50       480 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 42720016 my $class = shift;
521 87         580 my %args = @_;
522 87         325 my %args_backup = @_;
523              
524 87 100       443 if ( exists $args{count} )
525             {
526             # count
527 20         54 my $n = $args{count};
528 20         45 delete $args{count};
529             my $count_inf = $class->recur( %args )->{set}
530 20         114 ->select( count => $n );
531 20         5922718 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         250 my %tmp_args = @_;
538 67         178 delete $tmp_args{dtstart};
539 67         154 delete $tmp_args{dtend};
540 67         325 my $recur_str = _param_str(%tmp_args);
541              
542             # dtstart / dtend / until
543             my $span =
544             exists $args{dtstart} ?
545 67 100       598 DateTime::Span->from_datetimes( start => $args{dtstart} ) :
546             DateTime::Set->empty_set->complement;
547              
548             $span = $span->complement(
549             DateTime::Span->from_datetimes( after => delete $args{dtend} )
550 67 100       9983 ) if exists $args{dtend};
551              
552             $span = $span->complement(
553             DateTime::Span->from_datetimes( after => delete $args{until} )
554 67 100       8959 ) if exists $args{until};
555             # warn 'SPAN '. $span->{set};
556              
557 67 100       9044 $args{interval} = 1 unless $args{interval};
558              
559             # setup the "default time"
560             my $dtstart = exists $args{dtstart} ?
561             delete $args{dtstart} :
562 67 100       277 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       842 if ( $args{freq} eq 'daily' ) {
567 8 100 100     44 if ( exists $args{bymonth} &&
568             $args{interval} == 1 )
569             {
570 1         3 $args{freq} = 'yearly';
571 1 50       10 $args{bymonthday} = [ 1 .. 31 ] unless exists $args{bymonthday};
572             # warn "rewrite recur:"._param_str(%args);
573             }
574             }
575              
576 67         125 my $base_set;
577             my %by;
578              
579 67         307 $base_set = _recur_by_freq($args{freq},$dtstart,\%args);
580 67 50       46272 unless (defined $base_set) {
581 0         0 die "invalid freq ($args{freq})";
582             }
583              
584 67         139 delete $args{wkst}; # TODO: wkst
585              
586             # warn "\ncomplex recur:"._param_str(%args);
587              
588 67         149 %by = ();
589 67         122 my $has_day = 0;
590              
591 67         99 my $by_year_day;
592 67 50       209 if ( exists $args{byyearday} )
593             {
594 0         0 $by_year_day = _yearly_recurrence($dtstart, \%args);
595             }
596              
597 67         87 my $by_month_day;
598 67 100 66     339 if ( exists $args{bymonthday} ||
599             exists $args{bymonth} )
600             {
601 4         14 my %by = %args;
602 4 100       20 $by{byhour} = $args_backup{byhour} if $args_backup{byhour};
603 4 100       23 $by{byhour} = [ 0 .. 23 ] if $args{freq} eq 'hourly';
604 4 50       13 $by{byminute} = $args_backup{byminute} if $args_backup{byminute};
605 4 100       23 $by{byminute} = [ 0 .. 59 ] if $args{freq} eq 'minutely';
606 4 50       22 $by{bysecond} = $args_backup{bysecond} if $args_backup{bysecond};
607 4 50       15 $by{bysecond} = [ 0 .. 59 ] if $args{freq} eq 'secondly';
608 4         21 $by_month_day = _yearly_recurrence($dtstart, \%by);
609 4         3517 delete $args{bymonthday};
610 4         11 delete $args{bymonth};
611             }
612              
613 67         105 my $by_week_day;
614             # TODO: byweekno without byday
615 67 100 66     354 if ( exists $args{byday} ||
616             exists $args{byweekno} )
617             {
618 1         5 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       9 $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       5 $by{bysecond} = [ 0 .. 59 ] if $args{freq} eq 'secondly';
625 1         4 $by_week_day = _weekly_recurrence($dtstart, \%by);
626 1         803 delete $args{byday};
627 1         3 delete $args{byweekno};
628             }
629              
630 67         103 my $by_hour;
631 67 100       178 if ( exists $args{byhour} )
632             {
633 3         10 my %by = %args;
634 3 50       11 $by{byminute} = $args_backup{byminute} if $args_backup{byminute};
635 3 50       23 $by{byminute} = [ 0 .. 59 ] if $args{freq} eq 'minutely';
636 3 50       11 $by{bysecond} = $args_backup{bysecond} if $args_backup{bysecond};
637 3 50       10 $by{bysecond} = [ 0 .. 59 ] if $args{freq} eq 'secondly';
638 3         14 $by_hour = _daily_recurrence($dtstart, \%by);
639 3         2012 delete $args{byhour};
640             }
641              
642             # join the rules together
643              
644 67 50 33     470 $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     391 $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     975 $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     452 $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       561 if ( exists $args{bysetpos} ) {
662             $base_set = _recur_bysetpos (
663             freq => $args{freq},
664             interval => $args{interval},
665             bysetpos => $args{bysetpos},
666 3         22 recurrence => $base_set );
667 3         273 delete $args{bysetpos};
668             }
669              
670 67 50       340 $base_set = $base_set->intersection( $span )
671             if $span;
672              
673             # check for nonprocessed arguments
674 67         9787583 delete $args{freq};
675 67         191 my @args = %args;
676 67 50       251 die "these arguments are not implemented: "._param_str(%args) if @args;
677              
678 67         268 bless $base_set, 'DateTime::Set::ICal';
679 67         387 $base_set->set_ical( include => [ uc('rrule:'.$recur_str) ] );
680              
681 67         1046 return $base_set;
682             }
683              
684             __END__