File Coverage

blib/lib/Data/ICal/DateTime.pm
Criterion Covered Total %
statement 250 292 85.6
branch 69 86 80.2
condition 32 46 69.5
subroutine 33 37 89.1
pod 21 21 100.0
total 405 482 84.0


line stmt bran cond sub pod time code
1             package Data::ICal::DateTime;
2              
3 9     9   267092 use strict;
  9         16  
  9         316  
4 9     9   3631 use Clone;
  9         20275  
  9         428  
5 9     9   3486 use Data::ICal;
  9         195311  
  9         96  
6 9     9   5236 use DateTime::Set;
  9         1141676  
  9         266  
7 9     9   5494 use DateTime::Format::ICal;
  9         113294  
  9         510  
8              
9             our $VERSION = '0.81';
10              
11             # mmm, mixin goodness
12             sub import {
13 9     9   97 my $class = shift;
14 9     9   88 no strict 'refs';
  9         12  
  9         292  
15 9     9   42 no warnings 'redefine';
  9         11  
  9         25277  
16 9         31 *Data::ICal::events = \&events;
17 9         17 *Data::ICal::collapse = \&collapse;
18 9         30 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         375 *{"Data::ICal::Entry::Event::$sub"} = \&$sub;
  234         677  
23             }
24 9         2018 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 object with a method to return all events.
91              
92             If a L, L or L 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 923008 my $self = shift;
111 14         28 my $set = shift;
112 14         20 my $period = shift;
113              
114              
115 14         21 my @events = grep { $_->ical_entry_type eq 'VEVENT' } @{$self->entries};
  531         1475  
  14         93  
116              
117             # NOTE: this won't normalise events
118 14 100       296 return @events if (!$set);
119 8         20 @events = map { $_->explode($set) } @events;
  264         624  
120 8         40 @events = $self->collapse(@events);
121              
122 8 100       61 return @events unless defined $period;
123 2         4 return map { $_->split_up($period) } @events;
  22         107  
124              
125             }
126              
127             =head2 collapse
128              
129             Provides a L object with a method to collapse Cs.
130              
131             Given a list of events, some of which might have Cs,
132             return a list of events with all recurrences within C and all
133             Cs handled correctly.
134              
135             Used internally by C.
136              
137             =cut
138              
139             sub collapse {
140 8     8 1 19 my ($self, @events) = @_;
141              
142 8         14 my %rid;
143              
144             my @recurs;
145 8         66 for (@events) {
146 55         90 my $uid = $_->uid;
147             # TODO: this feels very hacky
148 55 50       392 $uid = rand().{}.time unless defined $uid;
149 55         83 $_->uid($uid);
150 55 100       366 if ($_->recurrence_id) {
151 1         32 push @recurs, $_;
152             } else {
153 54         264 push @{$rid{$uid}}, $_;
  54         180  
154             }
155             }
156              
157 8         30 foreach my $e (@recurs) {
158 1         2 my $uid = $e->uid;
159 1         8 for (@{$rid{$uid}}) {
  1         2  
160 4 100       496 next unless $_->start == $e->recurrence_id;
161             # TODO: does this need to merge fields?
162 1         243 $_ = $e;
163             }
164             }
165 8         259 @events = ();
166 8         29 push @events, @{$rid{$_}} for keys %rid;
  26         45  
167 8         112 return @events;
168              
169              
170             }
171              
172              
173             =head2 start [new]
174              
175             Returns a L object representing the start time of this event.
176              
177             May return undef.
178              
179             If passed a L object will set that to be the new start time.
180              
181             =cut
182              
183             sub _make_dt_param {
184 1150     1150   1421 my $self = shift;
185 1150         1105 my $dt = shift;
186 1150         2387 my $tmp = $dt->clone->set_time_zone('floating');
187 1150         105483 my $new = DateTime::Format::ICal->format_datetime($tmp);
188 1150         32850 return [ $new, { TZID => $dt->time_zone_long_name } ];
189             }
190              
191             sub start {
192 1812     1812 1 10003 my $self = shift;
193 1812         1683 my $new = shift;
194              
195 1812 100       3560 if ($new) {
196 541         18357 delete $self->{properties}->{dtstart};
197 541         1097 $self->add_property(dtstart => $self->_make_dt_param($new));
198             }
199              
200              
201 1812   50     82849 my $dtstart = $self->property('dtstart') || return undef;
202 1812         11604 my $ret = DateTime::Format::ICal->parse_datetime($dtstart->[0]->value);
203              
204 1812 100       340788 eval { $ret->set_time_zone($dtstart->[0]->parameters->{TZID}) } if $dtstart->[0]->parameters->{TZID};
  1271         15100  
205              
206 1812         349969 return $ret;
207              
208             }
209              
210              
211             =head2 end
212              
213             Returns a L object representing the end time of this event.
214              
215             May return undef.
216              
217             If passed a L object will set that to be the new end time.
218              
219             =cut
220              
221              
222             sub end {
223 1632     1632 1 5310 my $self = shift;
224 1632         1480 my $new = shift;
225              
226             # iCal represents all-day events by using ;VALUE=DATE
227             # and setting DTEND=end_date + 1
228 1632         2727 my $all_day = $self->all_day;
229              
230 1632 100       3584 if ($new) {
231 609         19830 delete $self->{properties}->{dtend};
232 609         1429 my $update = $new->clone;
233 609 100       6432 if ($all_day) {
234 14         47 $update->add( days => 1);
235 14         6953 $update->set( hour => 0, minute => 0, second => 0 );
236             }
237 609         4874 $self->add_property( dtend => $self->_make_dt_param($update) );
238 609 100       90869 $self->property('dtend')->[0]->parameters->{VALUE} = 'DATE' if $all_day;
239              
240             }
241              
242              
243 1632   100     3876 my $dtend = $self->property('dtend') || return undef;
244 1464         8681 my $ret = DateTime::Format::ICal->parse_datetime($dtend->[0]->value);
245              
246 1464 100       283199 eval { $ret->set_time_zone($dtend->[0]->parameters->{TZID}) } if ($dtend->[0]->parameters->{TZID});
  1291         16547  
247 1464 100       264475 $ret->truncate(to => 'day' )->subtract( nanoseconds => 1 ) if $all_day;
248              
249 1464         220741 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 5941 my $self = shift;
266 2208         2039 my $new = shift;
267              
268             # TODO - should be able to make all day with just the start
269 2208         5248 my $dtend = $self->property('dtend');
270              
271 2208 100       11851 if (!$dtend) {
272 213 100       488 return 0 unless $new;
273 1         3 $dtend = $self->start->clone->add( days => 1 )->truncate(to => 'day' )->subtract( nanoseconds => 1 );
274 1         1208 $self->end($dtend);
275 1         4 $dtend = $self->property('dtend');
276             }
277              
278 1996   100     7656 my $cur = (defined $dtend && defined $dtend->[0]->parameters->{VALUE} && $dtend->[0]->parameters->{VALUE} eq 'DATE') || 0;
279              
280 1996 100 100     33789 if (defined $new && $new != $cur) {
281 41         102 my $end = $self->end;
282 41 100       148 if ($new == 0) {
283 39         175 delete $self->property('dtend')->[0]->parameters->{VALUE};
284             } else {
285 2         8 $self->property('dtend')->[0]->parameters->{VALUE} = 'DATE';
286             }
287 41         864 $self->end($end);
288 41         171 $cur = $new;
289             }
290              
291 1996         2920 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 4634 my $self = shift;
312 34         62 my $new = shift;
313              
314 34         75 my $end = $self->end;
315 34 100       148 my $cur = (defined $end)? 0 : 1;
316 34 100 66     159 if (defined $new && $new != $cur) {
317             # it is floating - delete the end
318 4 100       10 if ($new) {
319 2         11 delete $self->{properties}->{dtend};
320             # it's not floating - simulate end as 1 nanosecond before midnight after the start
321             } else {
322 2         7 my $dtend = $self->start->clone->add( days => 1 )->truncate(to => 'day' )->subtract( nanoseconds => 1 );
323 2         2865 $self->end($dtend);
324             }
325 4         6 $cur = $new;
326             }
327              
328 34         188 return $cur;
329              
330             }
331              
332             =head2 duration
333              
334             Returns a L object representing the duration of this
335             event.
336              
337             May return undef.
338              
339             If passed a L object will set that to be the new
340             duration.
341              
342             =cut
343              
344             sub duration {
345 394     394 1 2700 my $self = shift;
346 394         355 my $new = shift;
347              
348 394 50       649 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     816 my $duration = $self->property('duration') || return undef;
354 155         1005 return DateTime::Format::ICal->parse_duration($duration->[0]->value);
355             }
356              
357              
358             =head2 period
359              
360             Returns a L object representing the period of this
361             event.
362              
363             May return undef.
364              
365             If passed a L object will set that to be the new
366             period.
367              
368             =cut
369              
370             sub period {
371 392     392 1 351 my $self = shift;
372 392         406 my $new = shift;
373              
374 392 50       748 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     970 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 object representing the union of all the
390             Cs in this object.
391              
392             May return undef.
393              
394             If passed one or more L lists, L lists, Ls,
395             or Ls then set the recurrence rules to be those.
396              
397             =cut
398              
399             sub recurrence {
400 394     394 1 420 my $self = shift;
401              
402              
403 394         784 return $self->_rule_set('rrule', @_);
404             }
405              
406             =head2 rdate
407              
408             Returns a L object representing the set of all Cs in the object.
409              
410             May return undef.
411              
412             =cut
413              
414             sub rdate {
415 392     392 1 416 my $self = shift;
416              
417 392         653 return $self->_date_set('rdate', @_);
418             }
419              
420              
421             =head2 exrule
422              
423             Returns a L object representing the union of all the
424             Cs in this object.
425              
426             May return undef.
427              
428             If passed one or more L lists, L lists, Ls,
429             or Ls then set the recurrence exclusion rules to be those.
430              
431             =cut
432              
433              
434             sub exrule {
435 392     392 1 448 my $self = shift;
436              
437 392         804 return $self->_rule_set('exrule', @_);
438              
439             }
440              
441             =head2 exdate
442              
443             Returns a L object representing the set of all Cs in the object.
444              
445             May return undef.
446              
447             =cut
448              
449             sub exdate {
450 392     392 1 354 my $self = shift;
451              
452 392         528 return $self->_date_set('exdate', @_);
453             }
454              
455              
456              
457             sub _date_set {
458 784     784   635 my $self = shift;
459 784         711 my $name = shift;
460              
461              
462 784 50       1280 $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   681 my $self = shift;
478 786         796 my $name = shift;
479              
480 786 50       1368 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         579 my @recurrence;
491 786   50     1397 my $start = $self->start || return undef;
492             #my $tz = $start->time_zone;
493              
494 786         24545 $start = $start->clone;
495             #$start->set_time_zone("floating");
496              
497 786         9967 my $set = DateTime::Set->empty_set;
498 786 100       15033 $self->property($name) || return undef;
499 17         146 for (@{ $self->property($name) }) {
  17         39  
500 18         223 my $recur = DateTime::Format::ICal->parse_recurrence(recurrence => $_->value, dtstart => $start);
501             # $recur->set_time_zone($_->parameters->{TZID}) if $_->parameters->{TZID};
502 18         388540 $set = $set->union($recur);
503             }
504             # $set->set_time_zone($tz);
505 17         1289 return $set;
506              
507              
508             }
509              
510             =head2 recurrence_id
511              
512             Returns a L object representing the recurrence-id of this event.
513              
514             May return undef.
515              
516             If passed a L object will set that to be the new recurrence-id.
517              
518             =cut
519              
520             sub recurrence_id {
521 451     451 1 430 my $self = shift;
522 451         385 my $new = shift;
523              
524 451 50       722 if ($new) {
525 0         0 delete $self->{properties}->{'recurrence-id'};
526 0         0 $self->add_property('recurrence-id' => DateTime::Format::ICal->format_datetime($new));
527             }
528              
529              
530 451   100     811 my $rid = $self->property('recurrence-id') || return undef;
531 6         37 my $ret = DateTime::Format::ICal->parse_datetime($rid->[0]->value);
532              
533             # $ret->set_time_zone($rid->[0]->parameters->{TZID}) if $rid->[0]->parameters->{TZID};
534              
535 6         1082 return $ret;
536              
537             }
538              
539             sub _simple_property {
540 799     799   708 my $self = shift;
541 799         647 my $name = shift;
542 799         575 my $val = shift;
543              
544 799 100       1087 if ($val) {
545 55         166 delete $self->{properties}->{$name};
546 55         103 $self->add_property( $name => $val );
547             }
548              
549 799   50     7822 $val = $self->property($name) || return undef;
550 799         4421 return $val->[0]->value;
551              
552             }
553              
554              
555              
556             =head2 uid
557              
558             Returns the uid of this event.
559              
560             If passed a new value then sets that to be the new uid value.
561              
562             =cut
563              
564             sub uid {
565 505     505 1 861 my $self = shift;
566 505         817 return $self->_simple_property('uid', @_);
567             }
568              
569              
570             =head2 summary
571              
572             Returns a string representing the summary of this event.
573              
574             May return undef.
575              
576             If passed a new value then sets that to be the new summary (and will escape all relevant characters).
577              
578             =cut
579              
580             sub summary {
581 294     294 1 8022 my $self = shift;
582 294         387 return $self->_simple_property('summary', @_);
583             }
584              
585             =head2 description
586              
587             Returns a string representing the description of this event.
588              
589             May return undef.
590              
591             If passed a new value then sets that to be the new description (and will escape all relevant characters).
592              
593             =cut
594              
595              
596             sub description {
597 0     0 1 0 my $self = shift;
598 0         0 return $self->_simple_property('description', @_);
599             }
600              
601             =head2 url
602              
603             Returns a string representing the url of this event.
604              
605             May return undef.
606              
607             If passed a new value then sets that to be the new description (and will escape all relevant characters).
608              
609             =cut
610              
611             sub url {
612 0     0 1 0 my $self = shift;
613 0         0 return $self->_simple_property('url', @_);
614             }
615              
616              
617              
618             sub _escape {
619 0     0   0 my $string = shift;
620 0         0 $string =~ s!(\\|,|;)!\\$1!mg;
621 0         0 $string =~ s!\x0a!\\n!mg;
622 0         0 return $string;
623             }
624              
625             sub _unescape {
626 0     0   0 my $string = shift;
627 0         0 $string =~ s!\\n!\x0a!gm;
628 0         0 $string =~ s!(\\\\|\\,|\\;)!substr($1,-1)!gem;
  0         0  
629 0         0 return $string;
630             }
631              
632              
633             =head2 explode [period]
634              
635             Takes L, L or L and
636             returns an array of events.
637              
638             If this is not a recurring event, and it falls with the span, then it
639             will return one event with the dtstart and dtend properties set and no
640             other time information.
641              
642             If this is a recurring event then it will return all times that this
643             recurs within the span. All returned events will have the dtstart and
644             dtend properties set and no other time information.
645              
646             If C is optionally passed then events longer than C will
647             be exploded into multiple events.
648              
649             C can be any of the following
650              
651             year month week day hour minute second
652              
653             =cut
654              
655             # this is quite heavily based on 'wgo' in the bin/ directory of Text::vFile::asData
656             sub explode {
657 264     264 1 329 my $self = shift;
658 264         299 my $span = shift;
659 264         288 my $period = shift;
660 264         469 my %e = $self->_normalise;
661              
662              
663              
664              
665 264         499 my @events;
666              
667              
668              
669 264 100 100     1197 if (! $e{recur} && $e{span}->intersects($span) ) {
670 16         9643 my $event = $self->clone();
671 16         135 delete $event->{properties}->{$_} for qw(rrule exrule rdate exdate duration period);
672 16         52 $event->start($e{start});
673 16         53 $event->end($e{end});
674 16         32 push @events, $event;
675             }
676              
677              
678 264 100 66     48079 if($e{recur} && $e{recur}->intersects($span)) {
679 12         2313983 my $int_set = $e{recur}->intersection($span);
680              
681             # Change the event's recurrence details so that only the events
682             # inside the time span we're interested in are listed.
683 12         2287102 $e{recur} = $int_set;
684 12         813 my $it = $e{recur}->iterator;
685 12         807 while(my $dt = $it->next()) {
686 40 100 100     165961 next if $e{exrule} && $e{exrule}->contains($dt);
687 39 50 33     453560 next if $e{exdate} && $e{exdate}->contains($dt);
688 39         2800 my $event = $self->clone();
689 39         411 delete $event->{properties}->{$_} for qw(rrule exrule rdate exdate duration period);
690              
691 39         132 $event->start($dt);
692 39 100       140 if (defined $e{duration}) {
693 38         136 my $end = $dt + $e{duration};
694 38         23306 $event->end($end);
695             }
696 39         90 $event->all_day($self->all_day);
697 39         95 $event->original($self);
698 39         192 push @events, $event;
699              
700             }
701             }
702 264 50       3541 return @events if (!defined $period);
703 0         0 my @new;
704 0         0 push @new, $_->split_up($period) for @events;
705 0         0 return @new;
706             }
707              
708             =head2 original
709              
710             Store or fetch a reference to the original event this was derived from.
711              
712             =cut
713              
714             sub original {
715 525     525 1 734 my $self = shift;
716              
717 525 100       1480 $self->{_original} = $_[0] if @_;
718              
719 525         3628 return $self->{_original};
720             }
721              
722             =head2 split_up
723              
724             Split an n-period event into n 1-period events.
725              
726             =cut
727              
728             sub split_up {
729 24     24 1 56 my $event = shift;
730 24         42 my $period = shift;
731              
732 24 50       79 return ($event) if $event->floating;
733              
734 24         33 my @new;
735 24         87 my $span = DateTime::Span->from_datetimes( start => $event->start, end => $event->end );
736 24         7317 my $dur = DateTime::Duration->new("${period}s" => 1)->subtract( "nanoseconds" => 1 );
737             my $r = DateTime::Set->from_recurrence(
738             recurrence => sub {
739 623     623   619105 $_[0]->truncate(to => $period )->add("${period}s" => 1);
740             },
741 24         5108 span => $span);
742 24         28238 $r = $r->union(DateTime::Set->from_datetimes(dates => [$event->start]));
743              
744 24         13349 my $i = $r->iterator;
745 24         1274 while (my $dt = $i->next) {
746 491 100       58807 last if $dt >= $event->end; # && !$event->all_day;
747 484         78838 my $e = $event->clone;
748 484         2412 $e->start($dt);
749 484         1234 $e->all_day(0);
750 484         1044 $e->original($event);
751             # $e->all_day($event->all_day) if $period ne 'second' && $period ne 'minute' && $period ne 'day';
752              
753 484         1310 my $end = $dt->truncate( to => $period )->add( "${period}s" => 1 )->subtract( nanoseconds => 1 );
754 484         760164 $e->end($end);
755 484         2949 push @new, $e;
756             }
757             # If, say we have a one week and 1 day event and period is
758             # 'week' then need to truncate to one 1 week event and one
759             # day event.
760             # $end = $e{end} if ( defined $period && $e{end} < $end);
761 24         606 $new[-1]->end($event->end); # if !$event->all_day;
762 24         3260 return @new;
763             }
764              
765             =head2 is_in
766              
767             Takes L, L or L and
768             returns whether this event can fall within that time frame.
769              
770             =cut
771              
772             sub is_in {
773 128     128 1 272927 my $self = shift;
774 128         108 my $span = shift;
775              
776 128         209 my %e = $self->_normalise;
777              
778              
779 128   66     600 return ( ( !$e{recur} && $e{span}->intersects($span) ) ||
780             ( $e{recur} && $e{recur}->intersection($span) ) );
781              
782             }
783              
784             # return normalised information about this event
785             sub _normalise {
786 392     392   381 my $self = shift;
787              
788 392         492 my %e = ();
789              
790 392         653 $e{period} = $self->period;
791 392         2831 $e{start} = $self->start;
792 392         774 $e{end} = $self->end;
793 392         1525 $e{duration} = $self->duration;
794 392         14682 $e{recur} = $self->recurrence;
795 392         3783 $e{exrule} = $self->exrule;
796 392         3861 $e{rdate} = $self->rdate;
797 392         2061 $e{exdate} = $self->exdate;
798 392         1953 $e{rid} = $self->recurrence_id;
799 392         2049 $e{uid} = $self->uid;
800              
801              
802 392 50       3385 if (defined $e{period}) {
803 0 0 0     0 if (defined $e{start} || defined $e{end}) {
804 0         0 die "Found a period *and* a start or end:\n".$self->as_string;
805             }
806              
807 0         0 $e{start} = $e{period}->start;
808 0         0 $e{end} = $e{period}->end;
809              
810             }
811              
812              
813              
814 392 50       745 if (!defined $e{start}) {
815 0         0 die "Couldn't find start\n".$self->as_string;
816             }
817              
818 392 50 66     1404 if (defined $e{end} && defined $e{duration}) {
819 0         0 die "Found both end *and* duration:\n".$self->as_string;
820             }
821              
822              
823             # events can be floating
824             #if (!defined $e{end} && !defined $e{duration}) {
825             # die "Couldn't find end *or* duration:\n".$self->as_string;
826             #}
827              
828 392 100       718 if (defined $e{duration}) {
829 154         480 $e{end} = $e{start} + $e{duration};
830             }
831              
832 392 50       77149 if (defined $e{rdate}) {
833 0 0       0 $e{recur} = (defined $e{recur}) ? $e{recur}->union($e{rdate}) : $e{rdate};
834             }
835              
836 392   66     984 my $end = $e{end} || $e{start}->clone->add(seconds => 1 );
837 392         13238 $e{span} = DateTime::Span->from_datetimes( start => $e{start}, end => $end );
838              
839 392 100       89526 $e{duration} = $e{span}->duration if $e{end};
840              
841 392         70163 return %e;
842             }
843              
844              
845             =head1 AUTHOR
846              
847             Simon Wistow
848              
849             =head1 COPYING
850              
851             Copyright, 2005 Simon Wistow
852              
853             Distributed under the same terms as Perl itself.
854              
855             =head1 BUGS
856              
857             Potential timezone problems?
858              
859             =head1 SEE ALSO
860              
861             L, L, L, L, L
862              
863             =cut
864              
865             1;