support for recurring events added thanks to Matthias Gehre

git-svn-id: https://fhem.svn.sourceforge.net/svnroot/fhem/trunk/fhem@2247 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
borisneubert
2012-12-01 20:11:35 +00:00
parent da0cb51df9
commit 74e1ffdc58
2 changed files with 100 additions and 18 deletions

View File

@@ -27,6 +27,7 @@
- feature: FHEMWEB longpoll reconnect (Matthias) - feature: FHEMWEB longpoll reconnect (Matthias)
- bugfix: rename may overwrite other devices - bugfix: rename may overwrite other devices
- feature: FLOORPLAN longpoll (Matthias) - feature: FLOORPLAN longpoll (Matthias)
- feature: support for recurring events added in 57_Calendar.pm (Boris)
- 2012-10-28 (5.3) - 2012-10-28 (5.3)
- feature: added functions trim, ltrim, rtrim, UntoggleDirect, - feature: added functions trim, ltrim, rtrim, UntoggleDirect,

View File

@@ -22,10 +22,6 @@
# #
############################################################################## ##############################################################################
# Todos:
# Support recurring events
use strict; use strict;
use warnings; use warnings;
use HttpUtils; use HttpUtils;
@@ -246,16 +242,25 @@ sub modeChanged {
# converts a date/time string to the number of non-leap seconds since the epoch # converts a date/time string to the number of non-leap seconds since the epoch
# 20120520T185202Z: date/time string in ISO8601 format, time zone GMT # 20120520T185202Z: date/time string in ISO8601 format, time zone GMT
# 20121129T222200: date/time string in ISO8601 format, time zone local
# 20120520: a date string has no time zone associated # 20120520: a date string has no time zone associated
sub tm { sub tm {
my ($t)= @_; my ($t)= @_;
#main::debug "convert $t"; return undef if(!$t);
#main::debug "convert >$t<";
my ($year,$month,$day)= (substr($t,0,4), substr($t,4,2),substr($t,6,2)); my ($year,$month,$day)= (substr($t,0,4), substr($t,4,2),substr($t,6,2));
if(length($t)>8) { if(length($t)>8) {
my ($hour,$minute,$second)= (substr($t,9,2), substr($t,11,2),substr($t,13,2)); my ($hour,$minute,$second)= (substr($t,9,2), substr($t,11,2),substr($t,13,2));
my $z;
$z= substr($t,15,1) if(length($t) == 16);
#main::debug "$day.$month.$year $hour:$minute:$second $z";
if($z) {
return main::fhemTimeGm($second,$minute,$hour,$day,$month-1,$year-1900); return main::fhemTimeGm($second,$minute,$hour,$day,$month-1,$year-1900);
} else { } else {
#main::debug "$day $month $year"; return main::fhemTimeLocal($second,$minute,$hour,$day,$month-1,$year-1900);
}
} else {
#main::debug "$day.$month.$year";
return main::fhemTimeLocal(0,0,0,$day,$month-1,$year-1900); return main::fhemTimeLocal(0,0,0,$day,$month-1,$year-1900);
} }
} }
@@ -322,6 +327,17 @@ sub ts0 {
return sprintf("%02d.%02d.%2d %02d:%02d", $day,$month+1,$year-100,$hour,$minute); return sprintf("%02d.%02d.%2d %02d:%02d", $day,$month+1,$year-100,$hour,$minute);
} }
sub plusNMonths($$) {
my ($tm, $n)= @_;
my ($second,$minute,$hour,$day,$month,$year,$wday,$yday,$isdst)= localtime($tm);
#main::debug "Adding $n months to $day.$month.$year $hour:$minute:$second= " . ts($tm);
$month+= $n;
$year+= int($month / 12);
$month %= 12;
#main::debug " gives $day.$month.$year $hour:$minute:$second= " . ts(main::fhemTimeLocal($second,$minute,$hour,$day,$month,$year));
return main::fhemTimeLocal($second,$minute,$hour,$day,$month,$year);
}
sub fromVEvent { sub fromVEvent {
my ($self,$vevent)= @_; my ($self,$vevent)= @_;
@@ -332,6 +348,13 @@ sub fromVEvent {
$self->{lastModified}= tm($vevent->value("LAST-MODIFIED")); $self->{lastModified}= tm($vevent->value("LAST-MODIFIED"));
$self->{summary}= $vevent->value("SUMMARY"); $self->{summary}= $vevent->value("SUMMARY");
$self->{location}= $vevent->value("LOCATION"); $self->{location}= $vevent->value("LOCATION");
#Dates to exclude in reoccuring rule
my @exdate;
@exdate= split(",", $vevent->value("EXDATE")) if($vevent->value("EXDATE"));
@exdate = map { tm($_) } @exdate;
$self->{exdate} = \@exdate;
#$self->{summary}=~ s/;/,/g; #$self->{summary}=~ s/;/,/g;
# #
@@ -343,18 +366,26 @@ sub fromVEvent {
if($rrule) { if($rrule) {
my @rrparts= split(";", $rrule); my @rrparts= split(";", $rrule);
my %r= map { split("=", $_); } @rrparts; my %r= map { split("=", $_); } @rrparts;
#foreach my $k (keys %r) {
# main::debug "Rule part $k is $r{$k}"; foreach my $k (keys %r) {
#} if( $k ne "FREQ" and $k ne "INTERVAL" and $k ne "UNTIL" and $k ne "COUNT" and $k ne "BYMONTHDAY") {
my $freq= $r{"FREQ"}; main::Log 2, "Calendar: RRULE $rrule is not supported";
#
# weekly
#
if($freq eq "WEEKLY") {
# my @weekdays= split(",",$r{"BYDAY"});# BYDAY is not always set
} }
} }
$self->{freq} = $r{"FREQ"};
#According to RFC, interval defaults to 1
$self->{interval} = exists($r{"INTERVAL"}) ? $r{"INTERVAL"} : 1;
$self->{until} = tm($r{"UNTIL"}) if(exists($r{"UNTIL"}));
$self->{count} = $r{"COUNT"} if(exists($r{"COUNT"}));
$self->{bymonthday} = $r{"BYMONTHDAY"} if(exists($r{"BYMONTHDAY"}));
# advanceToNextOccurance until we are in the future
my $t = time();
while($self->{end} < $t and $self->advanceToNextOccurance()) { ; }
}
# alarms # alarms
my @valarms= grep { $_->{type} eq "VALARM" } @{$vevent->{entries}}; my @valarms= grep { $_->{type} eq "VALARM" } @{$vevent->{entries}};
@@ -427,6 +458,53 @@ sub endTime {
return ts($self->{end}); return ts($self->{end});
} }
sub advanceToNextOccurance {
my ($self) = @_;
# See RFC 2445 page 39 and following
return if(!exists($self->{freq})); #This event is not reoccuring
return if(exists($self->{count}) and $self->{count} == 0); #We are already at the last occurance
#There are no leap seconds in epoch time
#Valid values for freq: SECONDLY, MINUTELY, HOURLY, DAILY, WEEKLY, MONTHLY, YEARLY
my $nextstart = $self->{start};
do
{
if($self->{freq} eq "SECONDLY") {
$nextstart += $self->{interval};
} elsif($self->{freq} eq "MINUTELY") {
$nextstart += 60*$self->{interval};
} elsif($self->{freq} eq "HOURLY") {
$nextstart += 60*60*$self->{interval};
} elsif($self->{freq} eq "DAILY") {
$nextstart += 60*60*24*$self->{interval};
} elsif($self->{freq} eq "WEEKLY") {
$nextstart += 7*60*60*24*$self->{interval};
} elsif($self->{freq} eq "MONTHLY") {
# here we ignore BYMONTHDAY as we consider the day of month of $self->{start}
# to be equal to BYMONTHDAY.
$nextstart= plusNMonths($nextstart, $self->{interval});
} elsif($self->{freq} eq "YEARLY") {
$nextstart= plusNMonths($nextstart, 12*$self->{interval});
} else {
main::Log 1, "Calendar: event frequency '" . $self->{freq} . "' not implemented";
return;
}
# Loop if nextstart is in the "dates to exclude"
} while(exists($self->{exdate}) and ($nextstart ~~ $self->{exdate}));
#the UNTIL clause is inclusive, so $newt == $self->{until} is okey
return if(exists($self->{until}) and $nextstart > $self->{until});
$self->{count} -= 1 if(exists($self->{count}));
my $duration = $self->{end} - $self->{start};
$self->{start} = $nextstart;
$self->{end} = $self->{start} + $duration;
main::Log 5, "Next time of $self->{summary} is: start " . ts($self->{"start"}) . ", end " . ts($self->{"end"});
return 1;
}
# returns 1 if time is before alarm time and before start time, else 0 # returns 1 if time is before alarm time and before start time, else 0
sub isUpcoming { sub isUpcoming {
@@ -640,10 +718,12 @@ sub Calendar_CheckTimes($) {
# we now run over all events and update the readings # we now run over all events and update the readings
my @allevents= $eventsObj->events(); my @allevents= $eventsObj->events();
my @endedevents= grep { $_->isEnded($t) } @allevents;
foreach (@endedevents) { $_->advanceToNextOccurance(); }
my @upcomingevents= grep { $_->isUpcoming($t) } @allevents; my @upcomingevents= grep { $_->isUpcoming($t) } @allevents;
my @alarmedevents= grep { $_->isAlarmed($t) } @allevents; my @alarmedevents= grep { $_->isAlarmed($t) } @allevents;
my @startedevents= grep { $_->isStarted($t) } @allevents; my @startedevents= grep { $_->isStarted($t) } @allevents;
my @endedevents= grep { $_->isEnded($t) } @allevents;
my $event; my $event;
#main::debug "Updating modes..."; #main::debug "Updating modes...";
@@ -695,6 +775,7 @@ sub Calendar_GetUpdate($) {
my $url= $hash->{fhem}{url}; my $url= $hash->{fhem}{url};
my $ics= GetFileFromURLQuiet($url); my $ics= GetFileFromURLQuiet($url);
#my $ics= CustomGetFileFromURL(0, $url, undef, undef, 1);
if(!defined($ics)) { if(!defined($ics)) {
Log 1, "Calendar " . $hash->{NAME} . ": Could not retrieve file at URL"; Log 1, "Calendar " . $hash->{NAME} . ": Could not retrieve file at URL";
return 0; return 0;