File Coverage

blib/lib/Data/ICal/DateTime.pm
Criterion Covered Total %
statement 251 293 85.6
branch 71 88 80.6
condition 32 46 69.5
subroutine 33 37 89.1
pod 21 21 100.0
total 408 485 84.1


line stmt bran cond sub pod time code
1             package Data::ICal::DateTime;
2              
3 10     10   877348 use strict;
  10         21  
  10         248  
4 10     10   2254 use Clone;
  10         17458  
  10         363  
5 10     10   2122 use Data::ICal;
  10         195547  
  10         103  
6 10     10   3675 use DateTime::Set;
  10         3733300  
  10         275  
7 10     10   3058 use DateTime::Format::ICal;
  10         105341  
  10         587  
8              
9             our $VERSION = '0.82';
10              
11             # mmm, mixin goodness
12             sub import {
13 9     9   100 my $class = shift;
14 10     10   87 no strict 'refs';
  10         21  
  10         292  
15 10     10   57 no warnings 'redefine';
  10         87  
  10         23867  
16 9         37 *Data::ICal::events = \&events;
17 9         23 *Data::ICal::collapse = \&collapse;
18 9         27 foreach my $sub (qw(start end duration period summary description original
19             all_day floating recurrence recurrence_id rdate exrule exdate uid url
20             _simple_property _rule_set _date_set explode is_in _normalise split_up _escape _unescape _make_dt_param))
21             {
22 234         445 *{"Data::ICal::Entry::Event::$sub"} = \&$sub;
  234         715  
23             }
24 9         2246 push @Data::ICal::Entry::Event::ISA, 'Clone';
25             }
26              
27              
28              
29             =head1 NAME
30              
31             Data::ICal::DateTime - convenience methods for using Data::ICal with DateTime
32              
33             =head1 SYNPOSIS
34              
35             # performs mixin voodoo
36             use Data::ICal::DateTime;
37             my $cal = Data::ICal->new( filename => 'example.ics');
38              
39              
40             my $date1 = DateTime->new( year => 2005, month => 7, day => 01 );
41             my $date2 = DateTime->new( year => 2005, month => 7, day => 07 );
42             my $span = DateTime::Span->from_datetimes( start => $date1, end => $date2 );
43              
44             my @events = $cal->events(); # all VEVENTS
45             my @week = $cal->events($span); # just in that week
46             my @week = $cal->events($span,'day');# explode long events into days
47              
48             my $event = Data::ICal::Entry::Event->new();
49              
50             $event->start($start); # $start is a DateTime object
51             $event->end($end); # so is $end
52              
53             $event->all_day # is this an all day event
54              
55             $event->duration($duration); # $duration is DateTime::Duration
56             $event->recurrence($recurrence); # $reccurence is a DateTime list,
57             # a DateTime::Span list,
58             # a DateTime::Set,
59             # or a DateTime::SpanSet
60              
61             $event->start; # returns a DateTime object
62             $event->end; # ditto
63             $event->duration; # returns a DateTime::Duration
64             $event->recurrence; # returns a DateTime::Set
65             $event->period; # returns a DateTime::Span object
66             $event->rdate; # returns a DateTime::Set
67             $event->exrule; # returns a DateTime::Set
68             $event->exdate; # returns a DateTime::Set
69             $event->explode($span); # returns an array of sub events
70             # (if this is recurring);
71             $event->explode($span,'week'); # if any events are longer than a
72             # week then split them up
73             $event->is_in($span); # whether this event falls within a
74             # Set, Span, or SetSpan
75              
76              
77             $cal->add_entry($event);
78              
79             methods
80              
81              
82             =head1 DESCRIPTION
83              
84             =head1 METHODS
85              
86             =cut
87              
88             =head2 events [span] [period]
89              
90             Provides a L<Data::ICal> object with a method to return all events.
91              
92             If a L<DateTime::Set>, L<DateTime::Span> or L<DateTime::SpanSet> object
93             is passed then only the events that occur within that set will be
94             returned including expansion of all recurring events. All events will be
95             normalised to have a dtstart and dtend rather than any other method of
96             determining their start and stop time.
97              
98             Additionally you can pass a period string which can be one of the
99             following
100              
101             year month week day hour minute second
102              
103             This will explode an event into as many sub events as needed e.g a
104             period of 'day' will explode a 2-day event into 2 one day events with
105             the second starting just after the first
106              
107             =cut
108              
109             sub events {
110 14     14 1 1345446 my $self = shift;
111 14         29 my $set = shift;
112 14         49 my $period = shift;
113              
114              
115 14         28 my @events = grep { $_->ical_entry_type eq 'VEVENT' } @{$self->entries};
  531         1883  
  14         70  
116              
117             # NOTE: this won't normalise events
118 14 100       260 return @events if (!$set);
119 8         20 @events = map { $_->explode($set) } @events;
  264         651  
120 8         67 @events = $self->collapse(@events);
121              
122 8 100       58 return @events unless defined $period;
123 2         7 return map { $_->split_up($period) } @events;
  22         97  
124              
125             }
126              
127             =head2 collapse <events>
128              
129             Provides a L<Data::ICal> object with a method to collapse C<recurrence-id>s.
130              
131             Given a list of events, some of which might have C<recurrence-id>s,
132             return a list of events with all recurrences within C<span> and all
133             C<recurrence-id>s handled correctly.
134              
135             Used internally by C<events>.
136              
137             =cut
138              
139             sub collapse {
140 8     8 1 27 my ($self, @events) = @_;
141              
142 8         18 my %rid;
143              
144             my @recurs;
145 8         21 for (@events) {
146 55         128 my $uid = $_->uid;
147             # TODO: this feels very hacky
148 55 50       473 $uid = rand().{}.time unless defined $uid;
149 55         142 $_->uid($uid);
150 55 100       448 if ($_->recurrence_id) {
151 1         26 push @recurs, $_;
152             } else {
153 54         312 push @{$rid{$uid}}, $_;
  54         138  
154             }
155             }
156              
157 8         24 foreach my $e (@recurs) {
158 1         4 my $uid = $e->uid;
159 1         10 for (@{$rid{$uid}}) {
  1         3  
160 4 100       728 next unless $_->start == $e->recurrence_id;
161             # TODO: does this need to merge fields?
162 1         337 $_ = $e;
163             }
164             }
165 8         350 @events = ();
166 8         28 push @events, @{$rid{$_}} for keys %rid;
  26         45  
167 8         60 return @events;
168              
169              
170             }
171              
172              
173             =head2 start [new]
174              
175             Returns a L<DateTime> object representing the start time of this event.
176              
177             May return undef.
178              
179             If passed a L<DateTime> object will set that to be the new start time.
180              
181             =cut
182              
183             sub _make_dt_param {
184 1150     1150   1534 my $self = shift;
185 1150         1519 my $dt = shift;
186 1150         2190 my $tmp = $dt->clone->set_time_zone('floating');
187 1150         152227 my $new = DateTime::Format::ICal->format_datetime($tmp);
188 1150         40442 return [ $new, { TZID => $dt->time_zone_long_name } ];
189             }
190              
191             sub start {
192 1812     1812 1 10889 my $self = shift;
193 1812         2681 my $new = shift;
194              
195 1812 100       3902 if ($new) {
196 541         3632 delete $self->{properties}->{dtstart};
197 541         1349 $self->add_property(dtstart => $self->_make_dt_param($new));
198             }
199              
200              
201 1812   50     132825 my $dtstart = $self->property('dtstart') || return undef;
202 1812         13348 my $ret = DateTime::Format::ICal->parse_datetime($dtstart->[0]->value);
203              
204 1812 100       563283 eval { $ret->set_time_zone($dtstart->[0]->parameters->{TZID}) } if $dtstart->[0]->parameters->{TZID};
  1271         18751  
205              
206 1812         478192 return $ret;
207              
208             }
209              
210              
211             =head2 end
212              
213             Returns a L<DateTime> object representing the end time of this event.
214              
215             May return undef.
216              
217             If passed a L<DateTime> object will set that to be the new end time.
218              
219             =cut
220              
221              
222             sub end {
223 1632     1632 1 5400 my $self = shift;
224 1632         2151 my $new = shift;
225              
226             # iCal represents all-day events by using ;VALUE=DATE
227             # and setting DTEND=end_date + 1
228 1632         3115 my $all_day = $self->all_day;
229              
230 1632 100       3647 if ($new) {
231 609         3727 delete $self->{properties}->{dtend};
232 609         1499 my $update = $new->clone;
233 609 100       5846 if ($all_day) {
234 14         53 $update->add( days => 1);
235 14         10619 $update->set( hour => 0, minute => 0, second => 0 );
236             }
237 609         7414 $self->add_property( dtend => $self->_make_dt_param($update) );
238 609 100       147804 $self->property('dtend')->[0]->parameters->{VALUE} = 'DATE' if $all_day;
239              
240             }
241              
242              
243 1632   100     3660 my $dtend = $self->property('dtend') || return undef;
244 1464         9224 my $ret = DateTime::Format::ICal->parse_datetime($dtend->[0]->value);
245              
246 1464 100       443864 eval { $ret->set_time_zone($dtend->[0]->parameters->{TZID}) } if ($dtend->[0]->parameters->{TZID});
  1291         19126  
247 1464 100       379920 $ret->truncate(to => 'day' )->subtract( nanoseconds => 1 ) if $all_day;
248              
249 1464         321940 return $ret;
250             }
251              
252             =head2 all_day
253              
254             Returns 1 if event is all day or 0 if not.
255              
256             If no end has been set and 1 is passed then will set end to be a
257             nanosecond before midnight the next day.
258              
259             The have multi-day all-day events simply set the end time to be
260             nanosecond before midnight on the last day of the event.
261              
262             =cut
263              
264             sub all_day {
265 2208     2208 1 6030 my $self = shift;
266 2208         2711 my $new = shift;
267              
268             # TODO - should be able to make all day with just the start
269 2208         5231 my $dtend = $self->property('dtend');
270              
271 2208 100       13872 if (!$dtend) {
272 213 100       537 return 0 unless $new;
273 1         3 $dtend = $self->start->clone->add( days => 1 )->truncate(to => 'day' )->subtract( nanoseconds => 1 );
274 1         1823 $self->end($dtend);
275 1         3 $dtend = $self->property('dtend');
276             }
277              
278 1996   100     7198 my $cur = (defined $dtend && defined $dtend->[0]->parameters->{VALUE} && $dtend->[0]->parameters->{VALUE} eq 'DATE') || 0;
279              
280 1996 100 100     36574 if (defined $new && $new != $cur) {
281 41         80 my $end = $self->end;
282 41 100       115 if ($new == 0) {
283 39         115 delete $self->property('dtend')->[0]->parameters->{VALUE};
284             } else {
285 2         7 $self->property('dtend')->[0]->parameters->{VALUE} = 'DATE';
286             }
287 41         813 $self->end($end);
288 41         124 $cur = $new;
289             }
290              
291 1996         3263 return $cur;
292             }
293              
294             =head2 floating
295              
296             An event is considered floating if it has a start but no end. It is intended
297             to represent an event that is associated with a given calendar date and time
298             of day, such as an anniversary and should not be considered as taking up any
299             amount of time.
300              
301             Returns 1 if the evnt is floating and 0 if it isn't.
302              
303             If passed a 1 then will set the event to be floating by deleting the end time.
304              
305             If passed a 0 and no end is currently set then it will set end to be a
306             nanosecond before midnight the next day.
307              
308             =cut
309              
310             sub floating {
311 34     34 1 5449 my $self = shift;
312 34         59 my $new = shift;
313              
314 34         77 my $end = $self->end;
315 34 100       136 my $cur = (defined $end)? 0 : 1;
316 34 100 66     128 if (defined $new && $new != $cur) {
317             # it is floating - delete the end
318 4 100       13 if ($new) {
319 2         8 delete $self->{properties}->{dtend};
320             # it's not floating - simulate end as 1 nanosecond before midnight after the start
321             } else {
322 2         8 my $dtend = $self->start->clone->add( days => 1 )->truncate(to => 'day' )->subtract( nanoseconds => 1 );
323 2         3744 $self->end($dtend);
324             }
325 4         7 $cur = $new;
326             }
327              
328 34         169 return $cur;
329              
330             }
331              
332             =head2 duration
333              
334             Returns a L<DateTime::Duration> object representing the duration of this
335             event.
336              
337             May return undef.
338              
339             If passed a L<DateTime::Duration> object will set that to be the new
340             duration.
341              
342             =cut
343              
344             sub duration {
345 394     394 1 2117 my $self = shift;
346 394         538 my $new = shift;
347              
348 394 50       748 if ($new) {
349 0         0 delete $self->{properties}->{duration};
350 0         0 $self->add_property( duration => DateTime::Format::ICal->format_duration($new) );
351             }
352              
353 394   100     858 my $duration = $self->property('duration') || return undef;
354 155         1123 return DateTime::Format::ICal->parse_duration($duration->[0]->value);
355             }
356              
357              
358             =head2 period
359              
360             Returns a L<DateTime::Span> object representing the period of this
361             event.
362              
363             May return undef.
364              
365             If passed a L<DateTime::Span> object will set that to be the new
366             period.
367              
368             =cut
369              
370             sub period {
371 392     392 1 483 my $self = shift;
372 392         567 my $new = shift;
373              
374 392 50       729 if ($new) {
375 0         0 delete $self->{properties}->{period};
376 0         0 $self->add_property( period => DateTime::Format::ICal->format_period($new) );
377             }
378              
379 392   50     1033 my $period = $self->property('period') || return undef;
380 0         0 my $ret = DateTime::Format::ICal->parse_period($period->[0]->value);
381              
382             # $ret->set_time_zone($period->[0]->parameters->{TZID}) if ($period->[0]->parameters->{TZID});
383 0         0 return $ret;
384             }
385              
386              
387             =head2 recurrence
388              
389             Returns a L<DateTime::Set> object representing the union of all the
390             C<RRULE>s in this object.
391              
392             May return undef.
393              
394             If passed one or more L<DateTime> lists, L<DateTime::Span> lists, L<DateTime::Set>s,
395             or L<DateTime::SpanSet>s then set the recurrence rules to be those.
396              
397             =cut
398              
399             sub recurrence {
400 394     394 1 588 my $self = shift;
401              
402              
403 394         828 return $self->_rule_set('rrule', @_);
404             }
405              
406             =head2 rdate
407              
408             Returns a L<DateTime::Set> object representing the set of all C<RDATE>s in the object.
409              
410             May return undef.
411              
412             =cut
413              
414             sub rdate {
415 392     392 1 573 my $self = shift;
416              
417 392         839 return $self->_date_set('rdate', @_);
418             }
419              
420              
421             =head2 exrule
422              
423             Returns a L<DateTime::Set> object representing the union of all the
424             C<EXRULE>s in this object.
425              
426             May return undef.
427              
428             If passed one or more L<DateTime> lists, L<DateTime::Span> lists, L<DateTime::Set>s,
429             or L<DateTime::SpanSet>s then set the recurrence exclusion rules to be those.
430              
431             =cut
432              
433              
434             sub exrule {
435 392     392 1 573 my $self = shift;
436              
437 392         828 return $self->_rule_set('exrule', @_);
438              
439             }
440              
441             =head2 exdate
442              
443             Returns a L<DateTime::Set> object representing the set of all C<RDATE>s in the object.
444              
445             May return undef.
446              
447             =cut
448              
449             sub exdate {
450 392     392 1 523 my $self = shift;
451              
452 392         681 return $self->_date_set('exdate', @_);
453             }
454              
455              
456              
457             sub _date_set {
458 784     784   1020 my $self = shift;
459 784         1000 my $name = shift;
460              
461              
462 784 50       1397 $self->property($name) || return undef;
463 0         0 my @dates;
464 0         0 for (@{ $self->property($name) }) {
  0         0  
465 0         0 foreach my $bit (split /,/, $_->value) {
466 0         0 my $date = DateTime::Format::ICal->parse_datetime($bit);
467             # $date->set_time_zone($_->parameters->{TZID}) if $_->parameters->{TZID};
468 0         0 push @dates, $date;
469             }
470             }
471 0         0 return DateTime::Set->from_datetimes( dates => \@dates );
472              
473             }
474              
475              
476             sub _rule_set {
477 786     786   1042 my $self = shift;
478 786         1093 my $name = shift;
479              
480 786 50       1436 if (@_) {
481 0         0 delete $self->{properties}->{$name};
482 0         0 foreach my $rule (DateTime::Format::ICal->format_recurrence(@_)) {
483             #$rule =~ s!^$name:!!i;
484 0         0 $rule =~ s!^[^:]+:!!;
485 0         0 $self->add_properties( $name => $rule );
486             }
487             }
488              
489              
490 786         995 my @recurrence;
491 786   50     1336 my $start = $self->start || return undef;
492             #my $tz = $start->time_zone;
493              
494 786         4642 $start = $start->clone;
495             #$start->set_time_zone("floating");
496              
497 786         9689 my $set = DateTime::Set->empty_set;
498 786 100       17216 $self->property($name) || return undef;
499 17         126 for (@{ $self->property($name) }) {
  17         45  
500 18         202 my $recur = DateTime::Format::ICal->parse_recurrence(recurrence => $_->value, dtstart => $start);
501             # $recur->set_time_zone($_->parameters->{TZID}) if $_->parameters->{TZID};
502 18 100       466051 $recur->set_time_zone($start->time_zone) if !$start->time_zone->is_floating;
503 18         75909 $set = $set->union($recur);
504             }
505             # $set->set_time_zone($tz);
506 17         1246 return $set;
507              
508              
509             }
510              
511             =head2 recurrence_id
512              
513             Returns a L<DateTime> object representing the recurrence-id of this event.
514              
515             May return undef.
516              
517             If passed a L<DateTime> object will set that to be the new recurrence-id.
518              
519             =cut
520              
521             sub recurrence_id {
522 451     451 1 606 my $self = shift;
523 451         609 my $new = shift;
524              
525 451 50       778 if ($new) {
526 0         0 delete $self->{properties}->{'recurrence-id'};
527 0         0 $self->add_property('recurrence-id' => DateTime::Format::ICal->format_datetime($new));
528             }
529              
530              
531 451   100     835 my $rid = $self->property('recurrence-id') || return undef;
532 6         44 my $ret = DateTime::Format::ICal->parse_datetime($rid->[0]->value);
533              
534             # $ret->set_time_zone($rid->[0]->parameters->{TZID}) if $rid->[0]->parameters->{TZID};
535              
536 6         1816 return $ret;
537              
538             }
539              
540             sub _simple_property {
541 799     799   993 my $self = shift;
542 799         1031 my $name = shift;
543 799         961 my $val = shift;
544              
545 799 100       1400 if ($val) {
546 55         175 delete $self->{properties}->{$name};
547 55         140 $self->add_property( $name => $val );
548             }
549              
550 799   50     13133 $val = $self->property($name) || return undef;
551 799         5388 return $val->[0]->value;
552              
553             }
554              
555              
556              
557             =head2 uid
558              
559             Returns the uid of this event.
560              
561             If passed a new value then sets that to be the new uid value.
562              
563             =cut
564              
565             sub uid {
566 505     505 1 1196 my $self = shift;
567 505         891 return $self->_simple_property('uid', @_);
568             }
569              
570              
571             =head2 summary
572              
573             Returns a string representing the summary of this event.
574              
575             May return undef.
576              
577             If passed a new value then sets that to be the new summary (and will escape all relevant characters).
578              
579             =cut
580              
581             sub summary {
582 294     294 1 9983 my $self = shift;
583 294         420 return $self->_simple_property('summary', @_);
584             }
585              
586             =head2 description
587              
588             Returns a string representing the description of this event.
589              
590             May return undef.
591              
592             If passed a new value then sets that to be the new description (and will escape all relevant characters).
593              
594             =cut
595              
596              
597             sub description {
598 0     0 1 0 my $self = shift;
599 0         0 return $self->_simple_property('description', @_);
600             }
601              
602             =head2 url
603              
604             Returns a string representing the url of this event.
605              
606             May return undef.
607              
608             If passed a new value then sets that to be the new description (and will escape all relevant characters).
609              
610             =cut
611              
612             sub url {
613 0     0 1 0 my $self = shift;
614 0         0 return $self->_simple_property('url', @_);
615             }
616              
617              
618              
619             sub _escape {
620 0     0   0 my $string = shift;
621 0         0 $string =~ s!(\\|,|;)!\\$1!mg;
622 0         0 $string =~ s!\x0a!\\n!mg;
623 0         0 return $string;
624             }
625              
626             sub _unescape {
627 0     0   0 my $string = shift;
628 0         0 $string =~ s!\\n!\x0a!gm;
629 0         0 $string =~ s!(\\\\|\\,|\\;)!substr($1,-1)!gem;
  0         0  
630 0         0 return $string;
631             }
632              
633              
634             =head2 explode <span> [period]
635              
636             Takes L<DateTime::Set>, L<DateTime::Span> or L<DateTime::SpanSet> and
637             returns an array of events.
638              
639             If this is not a recurring event, and it falls with the span, then it
640             will return one event with the dtstart and dtend properties set and no
641             other time information.
642              
643             If this is a recurring event then it will return all times that this
644             recurs within the span. All returned events will have the dtstart and
645             dtend properties set and no other time information.
646              
647             If C<period> is optionally passed then events longer than C<period> will
648             be exploded into multiple events.
649              
650             C<period> can be any of the following
651              
652             year month week day hour minute second
653              
654             =cut
655              
656             # this is quite heavily based on 'wgo' in the bin/ directory of Text::vFile::asData
657             sub explode {
658 264     264 1 421 my $self = shift;
659 264         658 my $span = shift;
660 264         340 my $period = shift;
661 264         520 my %e = $self->_normalise;
662              
663              
664              
665              
666 264         518 my @events;
667              
668              
669              
670 264 100 100     1084 if (! $e{recur} && $e{span}->intersects($span) ) {
671 16         12725 my $event = $self->clone();
672 16         110 delete $event->{properties}->{$_} for qw(rrule exrule rdate exdate duration period);
673 16         54 $event->start($e{start});
674 16         49 $event->end($e{end});
675 16         40 push @events, $event;
676             }
677              
678              
679 264 100 66     67122 if($e{recur} && $e{recur}->intersects($span)) {
680 12         2496213 my $int_set = $e{recur}->intersection($span);
681              
682             # Change the event's recurrence details so that only the events
683             # inside the time span we're interested in are listed.
684 12         2623599 $e{recur} = $int_set;
685 12         83 my $it = $e{recur}->iterator;
686 12         781 while(my $dt = $it->next()) {
687 40 100 100     232207 next if $e{exrule} && $e{exrule}->contains($dt);
688 39 50 33     647726 next if $e{exdate} && $e{exdate}->contains($dt);
689 39         2475 my $event = $self->clone();
690 39         373 delete $event->{properties}->{$_} for qw(rrule exrule rdate exdate duration period);
691              
692 39         133 $event->start($dt);
693 39 100       121 if (defined $e{duration}) {
694 38         146 my $end = $dt + $e{duration};
695 38         36563 $event->end($end);
696             }
697 39         97 $event->all_day($self->all_day);
698 39         108 $event->original($self);
699 39         145 push @events, $event;
700              
701             }
702             }
703 264 50       3140 return @events if (!defined $period);
704 0         0 my @new;
705 0         0 push @new, $_->split_up($period) for @events;
706 0         0 return @new;
707             }
708              
709             =head2 original <event>
710              
711             Store or fetch a reference to the original event this was derived from.
712              
713             =cut
714              
715             sub original {
716 525     525 1 721 my $self = shift;
717              
718 525 100       3670 $self->{_original} = $_[0] if @_;
719              
720 525         848 return $self->{_original};
721             }
722              
723             =head2 split_up <period>
724              
725             Split an n-period event into n 1-period events.
726              
727             =cut
728              
729             sub split_up {
730 24     24 1 55 my $event = shift;
731 24         48 my $period = shift;
732              
733 24 50       75 return ($event) if $event->floating;
734              
735 24         43 my @new;
736 24         74 my $span = DateTime::Span->from_datetimes( start => $event->start, end => $event->end );
737 24         7092 my $dur = DateTime::Duration->new("${period}s" => 1)->subtract( "nanoseconds" => 1 );
738             my $r = DateTime::Set->from_recurrence(
739             recurrence => sub {
740 623     623   915559 $_[0]->truncate(to => $period )->add("${period}s" => 1);
741             },
742 24         7665 span => $span);
743 24         37388 $r = $r->union(DateTime::Set->from_datetimes(dates => [$event->start]));
744              
745 24         15846 my $i = $r->iterator;
746 24         1405 while (my $dt = $i->next) {
747 491 100       51390 last if $dt >= $event->end; # && !$event->all_day;
748 484         79405 my $e = $event->clone;
749 484         2785 $e->start($dt);
750 484         1392 $e->all_day(0);
751 484         1236 $e->original($event);
752             # $e->all_day($event->all_day) if $period ne 'second' && $period ne 'minute' && $period ne 'day';
753              
754 484         1110 my $end = $dt->truncate( to => $period )->add( "${period}s" => 1 )->subtract( nanoseconds => 1 );
755 484         1196051 $e->end($end);
756 484         1915 push @new, $e;
757             }
758             # If, say we have a one week and 1 day event and period is
759             # 'week' then need to truncate to one 1 week event and one
760             # day event.
761             # $end = $e{end} if ( defined $period && $e{end} < $end);
762 24         629 $new[-1]->end($event->end); # if !$event->all_day;
763 24         2338 return @new;
764             }
765              
766             =head2 is_in <span>
767              
768             Takes L<DateTime::Set>, L<DateTime::Span> or L<DateTime::SpanSet> and
769             returns whether this event can fall within that time frame.
770              
771             =cut
772              
773             sub is_in {
774 128     128 1 353921 my $self = shift;
775 128         185 my $span = shift;
776              
777 128         237 my %e = $self->_normalise;
778              
779              
780             return ( ( !$e{recur} && $e{span}->intersects($span) ) ||
781 128   66     603 ( $e{recur} && $e{recur}->intersection($span) ) );
782              
783             }
784              
785             # return normalised information about this event
786             sub _normalise {
787 392     392   535 my $self = shift;
788              
789 392         587 my %e = ();
790              
791 392         764 $e{period} = $self->period;
792 392         3140 $e{start} = $self->start;
793 392         868 $e{end} = $self->end;
794 392         1538 $e{duration} = $self->duration;
795 392         21046 $e{recur} = $self->recurrence;
796 392         4169 $e{exrule} = $self->exrule;
797 392         4432 $e{rdate} = $self->rdate;
798 392         2513 $e{exdate} = $self->exdate;
799 392         2311 $e{rid} = $self->recurrence_id;
800 392         2599 $e{uid} = $self->uid;
801              
802              
803 392 50       4217 if (defined $e{period}) {
804 0 0 0     0 if (defined $e{start} || defined $e{end}) {
805 0         0 die "Found a period *and* a start or end:\n".$self->as_string;
806             }
807              
808 0         0 $e{start} = $e{period}->start;
809 0         0 $e{end} = $e{period}->end;
810              
811             }
812              
813              
814              
815 392 50       768 if (!defined $e{start}) {
816 0         0 die "Couldn't find start\n".$self->as_string;
817             }
818              
819 392 50 66     1175 if (defined $e{end} && defined $e{duration}) {
820 0         0 die "Found both end *and* duration:\n".$self->as_string;
821             }
822              
823              
824             # events can be floating
825             #if (!defined $e{end} && !defined $e{duration}) {
826             # die "Couldn't find end *or* duration:\n".$self->as_string;
827             #}
828              
829 392 100       735 if (defined $e{duration}) {
830 154         480 $e{end} = $e{start} + $e{duration};
831             }
832              
833 392 50       133795 if (defined $e{rdate}) {
834 0 0       0 $e{recur} = (defined $e{recur}) ? $e{recur}->union($e{rdate}) : $e{rdate};
835             }
836              
837 392   66     1033 my $end = $e{end} || $e{start}->clone->add(seconds => 1 );
838 392         4131 $e{span} = DateTime::Span->from_datetimes( start => $e{start}, end => $end );
839              
840 392 100       106284 $e{duration} = $e{span}->duration if $e{end};
841              
842 392         82023 return %e;
843             }
844              
845              
846             =head1 AUTHOR
847              
848             Simon Wistow <simon@thegestalt.org>
849              
850             =head1 COPYING
851              
852             Copyright, 2005 Simon Wistow
853              
854             Distributed under the same terms as Perl itself.
855              
856             =head1 BUGS
857              
858             Potential timezone problems?
859              
860             =head1 SEE ALSO
861              
862             L<DateTime>, L<DateTime::Set>, L<Data::ICal>, L<Text::vFile::asData>, L<iCal::Parser>
863              
864             =cut
865              
866             1;