| 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; |