From 8dcde68ccf4b37fb386418979812d28628add7c2 Mon Sep 17 00:00:00 2001 From: borisneubert Date: Mon, 2 Apr 2018 20:09:57 +0000 Subject: [PATCH] 57_Calendar: fix weekly events, get full all, defaultTimeFormat git-svn-id: https://svn.fhem.de/fhem/trunk@16539 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 1 + fhem/FHEM/57_Calendar.pm | 311 ++++++++++++++++++++++----------------- 2 files changed, 174 insertions(+), 138 deletions(-) diff --git a/fhem/CHANGED b/fhem/CHANGED index 3f271e7dc..5e8813f5b 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - bugfix: 57_Calendar: fix weekly events, get full all, defaultTimeFormat - change: 93_DbLog: V3.10.0, addLog considers DbLogExclude in Devices, keyword "!useExcludes" to switch off considering DbLogExclude in addLog, DbLogExclude & DbLogInclude diff --git a/fhem/FHEM/57_Calendar.pm b/fhem/FHEM/57_Calendar.pm index e152b6a74..e7c2e73d0 100644 --- a/fhem/FHEM/57_Calendar.pm +++ b/fhem/FHEM/57_Calendar.pm @@ -1316,12 +1316,19 @@ sub addEventLimited($$$) { return -1 if($event->start()< $t+eventsLimitMinus); return 1 if($event->start()> $t+eventsLimitPlus); - #main::Debug " addEvent: " . $event->asFull(); $self->addEvent($event); + #main::Debug " addEventLimited: " . $event->asDebug(); return 0; } +# 0= SU ... 6= SA +sub weekdayOf($$) { + my ($self, $t)= @_; + my (undef, undef, undef, undef, undef, undef, $weekday, undef, undef) = localtime($t); + return $weekday; +} + sub createSingleEvent($$$$) { my ($self, $nextstart, $onCreateEvent)= @_; @@ -1359,8 +1366,108 @@ sub createSingleEvent($$$$) { return $event; } +sub excludeByExdate($$) { + my ($self, $event)= @_; + my $skip= 0; + if($self->hasKey('EXDATE')) { + foreach my $exdate (@{$self->values("EXDATE")}) { + if($self->tm($exdate) == $event->start()) { + $skip++; + $event->setNote("EXDATE: $exdate"); + $self->addSkippedEvent($event); + last; + } + } # end of foreach exdate + } # end of EXDATE checking + return $skip; +} + +sub excludeByReference($$$) { + my ($self, $event, $veventsref)= @_; + my $skip= 0; + # check if superseded by out-of-series event + if($self->hasReferences()) { + foreach my $id (@{$self->references()}) { + my $vevent= $veventsref->{$id}; + my $recurrenceid= $vevent->value("RECURRENCE-ID"); + my $originalstart= $vevent->tm($recurrenceid); + if($originalstart == $event->start()) { + $event->setNote("RECURRENCE-ID: $recurrenceid"); + $self->addSkippedEvent($event); + $skip++; + last; + } + } + } + return $skip; +} + +sub excludeByRdate($$) { + my ($self, $event)= @_; + my $skip= 0; + # check if excluded by a duplicate RDATE + # this is only to avoid duplicates from previously added RDATEs + if($self->hasKey('RDATE')) { + foreach my $rdate (@{$self->values("RDATE")}) { + if($self->tm($rdate) == $event->start()) { + $event->setNote("RDATE: $rdate"); + $self->addSkippedEvent($event); + $skip++; + last; + } + } + } + return $skip; +} + +# we return 0 if the storage limit is exceeded or the number of occurances is reached +# we return 1 else no matter if this evevent was added or skipped +sub addOrSkipSeriesEvent($$$$$$) { + my ($self, $event, $t0, $until, $count, $veventsref)= @_; + + #main::Debug " addOrSkipSeriesEvent: " . $event->asDebug(); + return if($event->{start} > $until); # return if we are after end of series + + my $skip= 0; + + # check if superseded by out-of-series event + $skip+= $self->excludeByReference($event, $veventsref); + + # RFC 5545 p. 120 + # The final recurrence set is generated by gathering all of the + # start DATE-TIME values generated by any of the specified "RRULE" + # and "RDATE" properties, and then excluding any start DATE-TIME + # values specified by "EXDATE" properties. This implies that start + # DATE-TIME values specified by "EXDATE" properties take precedence + # over those specified by inclusion properties (i.e., "RDATE" and + # "RRULE"). Where duplicate instances are generated by the "RRULE" + # and "RDATE" properties, only one recurrence is considered. + # Duplicate instances are ignored. + + # check if excluded by EXDATE + $skip+= $self->excludeByExdate($event); + + # check if excluded by a duplicate RDATE + # this is only to avoid duplicates from previously added RDATEs + $skip+= $self->excludeByRdate($event); + + if(!$skip) { + # add event + # and return if we exceed storage limit + my $x= $self->addEventLimited($t0, $event); + #main::Debug "addEventLimited returned $x"; + return 0 if($x> 0); + #return 0 if($self->addEventLimited($t0, $event) > 0); + } + + my $occurances= scalar(@{$self->{events}}); + #main::Debug("$occurances occurances so far"); + return($occurances< $count); + +} + sub createEvents($$$%) { - my ($self, $t, $onCreateEvent, %vevents)= @_; + my ($self, $t0, $onCreateEvent, %vevents)= @_; # t0 is today (for limits) $self->clearEvents(); $self->clearSkippedEvents(); @@ -1419,9 +1526,8 @@ sub createEvents($$$%) { } if(!$skip) { # add event - # and return if we exceed storage limit $event->setNote("RDATE: $rdate"); - $self->addEventLimited($t, $event); + $self->addEventLimited($t0, $event); } } } @@ -1429,156 +1535,81 @@ sub createEvents($$$%) { # # now we build the series # + #main::Debug "building series..."; # first event in the series my $event= $self->createSingleEvent(undef, $onCreateEvent); - my $n= 0; + return if(!$self->addOrSkipSeriesEvent($event, $t0, $until, $count, \%vevents)); + my $nextstart = $event->{start}; + #main::Debug "start: " . $event->ts($nextstart); + if(($freq eq "WEEKLY") && ($byday ne "")) { + # special handling for WEEKLY and BYDAY - while(1) { - my $skip= 0; + # BYDAY with prefix (e.g. -1SU or 2MO) is not recognized + #main::Debug "weekly event, BYDAY= $byday"; + my @bydays= split(',', $byday); - # check if superseded by out-of-series event - if($self->hasReferences()) { - foreach my $id (@{$self->references()}) { - my $vevent= $vevents{$id}; - my $recurrenceid= $vevent->value("RECURRENCE-ID"); - my $originalstart= $vevent->tm($recurrenceid); - if($originalstart == $event->start()) { - $event->setNote("RECURRENCE-ID: $recurrenceid"); - $self->addSkippedEvent($event); - $skip++; - last; - } - } + # we assume a week from MO to SU + # we need to cover situations similar to: + # BYDAY= TU,WE,TH and start is WE or end is WE + + # loop over days, skip over weeks + # e.g. TH, FR, SA, SU / ... / MO, TU, WE + while(1) { + # next day + $nextstart= plusNSeconds($nextstart, 24*60*60, 1); + my $weekday= $self->weekdayOf($nextstart); + # if we reach MO, then skip ($interval-1) weeks + $nextstart= plusNSeconds($nextstart, 7*24*60*60, $interval-1) if($weekday==1); + #main::Debug "Skip to: start " . $event->ts($nextstart) . " = " . $weekdays[$weekday]; + if($weekdays[$weekday] ~~ @bydays) { + my $event= $self->createSingleEvent($nextstart, $onCreateEvent); + return if(!$self->addOrSkipSeriesEvent($event, $t0, $until, $count, \%vevents)); + } } - - # RFC 5545 p. 120 - # The final recurrence set is generated by gathering all of the - # start DATE-TIME values generated by any of the specified "RRULE" - # and "RDATE" properties, and then excluding any start DATE-TIME - # values specified by "EXDATE" properties. This implies that start - # DATE-TIME values specified by "EXDATE" properties take precedence - # over those specified by inclusion properties (i.e., "RDATE" and - # "RRULE"). Where duplicate instances are generated by the "RRULE" - # and "RDATE" properties, only one recurrence is considered. - # Duplicate instances are ignored. - - # check if excluded by EXDATE - if($self->hasKey('EXDATE')) { - foreach my $exdate (@{$self->values("EXDATE")}) { - if($self->tm($exdate) == $event->start()) { - $event->setNote("EXDATE: $exdate"); - $self->addSkippedEvent($event); - $skip++; - last; - } - } - } - - # check if excluded by a duplicate RDATE - # this is only to avoid duplicates from previously added RDATEs - if($self->hasKey('RDATE')) { - foreach my $rdate (@{$self->values("RDATE")}) { - if($self->tm($rdate) == $event->start()) { - $event->setNote("RDATE: $rdate"); - $self->addSkippedEvent($event); - $skip++; - last; - } - } - } - - return if($event->{start} > $until); # return if we are after end of series - if(!$skip) { - # add event - # and return if we exceed storage limit - return if($self->addEventLimited($t, $event) > 0); - } - $n++; - return if($n>= $count); # return if we exceeded occurances - - # advance to next occurence - my $nextstart = $event->{start}; - if($freq eq "SECONDLY") { - $nextstart = plusNSeconds($nextstart, 1, $interval); - } elsif($freq eq "MINUTELY") { - $nextstart = plusNSeconds($nextstart, 60, $interval); - } elsif($freq eq "HOURLY") { - $nextstart = plusNSeconds($nextstart, 60*60, $interval); - } elsif($freq eq "DAILY") { - $nextstart = plusNSeconds($nextstart, 24*60*60, $interval); - } elsif($freq eq "WEEKLY") { - # special handling for WEEKLY and BYDAY - #main::Debug "weekly event, BYDAY= $byday"; - if($byday ne "") { - # BYDAY with prefix (e.g. -1SU or 2MO) is not recognized - my @bydays= split(',', $byday); - # we skip interval-1 weeks - $nextstart = plusNSeconds($nextstart, 7*24*60*60, $interval-1) if ($n > 1); - my ($msec, $mmin, $mhour, $mday, $mmon, $myear, $mwday, $yday, $isdat); - my $currentstart = 0; - ($msec, $mmin, $mhour, $mday, $mmon, $myear, $mwday, $yday, $isdat) = localtime($nextstart); - $nextstart = plusNSeconds($nextstart, 24*60*60, 7-$mwday) if ($n > 1 and $mwday > 0); # advance to next start of week - do { - $nextstart = plusNSeconds($nextstart, 24*60*60, 1); # forward day by day - ($msec, $mmin, $mhour, $mday, $mmon, $myear, $mwday, $yday, $isdat) = - localtime($nextstart); - #main::Debug "Skip to: start " . $event->ts($nextstart) . " = " . $weekdays[$mwday]; - if($weekdays[$mwday] ~~ @bydays) { - if (($currentstart > 0) && ($currentstart<= $until)) { - $skip = 0; - $event = $self->createSingleEvent($currentstart, $onCreateEvent); - if($self->hasKey('EXDATE')) { - foreach my $exdate (@{$self->values("EXDATE")}) { - if($self->tm($exdate) == $event->start()) { - $event->setNote("EXDATE: $exdate"); - $self->addSkippedEvent($event); - $skip++; - last; - } - } - } # end of EXDATE - $self->addEventLimited($t, $event) if ($skip == 0); # add current event - } - $currentstart = $nextstart; - } - #main::Debug "weekday= " . $weekdays[$mwday] . "($mwday), smartmatch " . join(" ",@bydays) ."= " . ($weekdays[$mwday] ~~ @bydays ? "yes" : "no"); - } until($weekdays[$mwday] eq "SU"); - #main::Debug $self->asString(); - $nextstart = $currentstart; - } # end of WEKKLY BYDAY - else { + } else { + # handling for events with equal time spacing + while(1) { + # advance to next occurance + if($freq eq "SECONDLY") { + $nextstart = plusNSeconds($nextstart, 1, $interval); + } elsif($freq eq "MINUTELY") { + $nextstart = plusNSeconds($nextstart, 60, $interval); + } elsif($freq eq "HOURLY") { + $nextstart = plusNSeconds($nextstart, 60*60, $interval); + } elsif($freq eq "DAILY") { + $nextstart = plusNSeconds($nextstart, 24*60*60, $interval); + } elsif($freq eq "WEEKLY") { # default WEEKLY handling $nextstart = plusNSeconds($nextstart, 7*24*60*60, $interval); + } elsif($freq eq "MONTHLY") { + if ( $byday ne "" ) { + $nextstart = getNextMonthlyDateByDay( $nextstart, $byday, $interval ); + } else { + # here we ignore BYMONTHDAY as we consider the day of month of $self->{start} + # to be equal to BYMONTHDAY. + $nextstart= plusNMonths($nextstart, $interval); + } + } elsif($freq eq "YEARLY") { + $nextstart= plusNMonths($nextstart, 12*$interval); + } else { + main::Log3 undef, 2, "Calendar: event frequency '$freq' not implemented"; + return; } - } elsif($freq eq "MONTHLY") { - if ( $byday ne "" ) { - $nextstart = getNextMonthlyDateByDay( $nextstart, $byday, $interval ); - } - else { - # here we ignore BYMONTHDAY as we consider the day of month of $self->{start} - # to be equal to BYMONTHDAY. - $nextstart= plusNMonths($nextstart, $interval); - } - } elsif($freq eq "YEARLY") { - $nextstart= plusNMonths($nextstart, 12*$interval); - } else { - main::Log3 undef, 2, "Calendar: event frequency '$freq' not implemented"; - return; + # the next event + #main::Debug "Skip to: start " . $event->ts($nextstart); + $event= $self->createSingleEvent($nextstart, $onCreateEvent); + return if(!$self->addOrSkipSeriesEvent($event, $t0, $until, $count, \%vevents)); } - # the next event - $event= $self->createSingleEvent($nextstart, $onCreateEvent); - } - } else { # # single event # my $event= $self->createSingleEvent(undef, $onCreateEvent); - $self->addEventLimited($t, $event); + $self->addEventLimited($t0, $event); } } @@ -1887,7 +1918,7 @@ sub Calendar_Get($@) { # attr myCalendar defaultFormat my $format= AttrVal($name, "defaultFormat", '"$T1 $D $S"'); - my $timeFormat= AttrVal($name, "defaultTimeFormat",'%d.%m %H:%M'); + my $timeFormat= AttrVal($name, "defaultTimeFormat",'%d.%m.%Y %H:%M'); my @filters= (); my $next= undef; @@ -2018,7 +2049,7 @@ sub Calendar_Get($@) { my @uids= split(";", $hash->{READINGS}{$filter}{VAL}); $param= \@uids; } elsif($filter eq "all") { - $filterref= undef; + $filterref= \&filter_true; } elsif($filter eq "next") { $filterref= \&filter_notend; $param= { }; # reference to anonymous (unnamed) empty hash, thus $ in $param @@ -2206,6 +2237,10 @@ sub Calendar_GetSecondsFromTimeSpec($) { ################################### # Filters +sub filter_true($$) { + return 1; +} + sub filter_mode($$) { my ($event,$value)= @_; my $hit;