From 14dde084755a8b15004d59bb6be5cc7a3726a8bf Mon Sep 17 00:00:00 2001 From: Peter Stephenson Date: Mon, 14 Jun 2010 13:01:41 +0000 Subject: 28038: improved handling of recurring events in calendar system --- Functions/Calendar/calendar_parse | 155 +++++++++++++++++++++++++++++--------- 1 file changed, 118 insertions(+), 37 deletions(-) (limited to 'Functions/Calendar/calendar_parse') diff --git a/Functions/Calendar/calendar_parse b/Functions/Calendar/calendar_parse index e53e97516..b08622a9d 100644 --- a/Functions/Calendar/calendar_parse +++ b/Functions/Calendar/calendar_parse @@ -1,6 +1,6 @@ # Parse the line passed down in the first argument as a calendar entry. # Sets the values parsed into the associative array reply, consisting of: -# time The time as an integer (as per EPOCHSECONDS) +# time The time as an integer (as per EPOCHSECONDS) of the (next) event. # text1 The text from the the line not including the date/time, but # including any WARN or RPT text. This is useful for rescheduling # events, since the keywords need to be retained in this case. @@ -10,11 +10,16 @@ # difference). # warnstr Any warning time as the original string (e.g. "5 mins"), not # including the WARN keyword. -# rpttime Any repeat/recurrence time (RPT keyword) as an integer, else empty. -# This is the time of the recurrence itself in EPOCHSECONDS units -# (as with a warning---not the difference between the events). +# schedrpttime The next scheduled recurrence (which may be cancelled +# or rescheduled). +# rpttime The actual occurrence time: the event may have been rescheduled, +# in which case this is the time of the actual event (for use in +# programming warnings etc.) rather than that of the normal +# recurrence (which is recorded by calendar_add as RECURRENCE). +# # rptstr Any repeat/recurrence time as the original string. -# text2 The text from the line with the date and keywords and values removed. +# text2 The text from the line with the date and other keywords and +# values removed. # # Note that here an "integer" is a string of digits, not an internally # formatted integer. @@ -26,9 +31,14 @@ emulate -L zsh setopt extendedglob -local REPLY REPLY2 +local vdatefmt="%Y%m%dT%H%M%S" + +local REPLY REPLY2 timefmt occurrence skip try_to_recover before after local -a match mbegin mend -integer now +integer now then replaced firstsched schedrpt +# Any text matching "OCCURRENCE " +# may occur multiple times. We set occurrences[]=disposition. +local -A occurrences autoload -U calendar_scandate @@ -45,51 +55,122 @@ fi # REPLY2 to the line with the date and time removed. calendar_scandate -as $1 || return 1 reply[time]=$(( REPLY )) +schedrpt=${reply[time]} reply[text1]=${REPLY2##[[:space:]]#} +reply[text2]=${reply[text1]} -reply[text2]=$reply[text1] - -integer changed=1 +while true; do -while (( changed )); do + case ${reply[text2]} in + # First check for a scheduled repeat time. If we don't find one + # we'll use the normal time. + ((#b)(*[[:space:]\#])RECURRENCE[[:space:]]##([^[:space:]]##)([[:space:]]*|)) + strftime -rs then $vdatefmt ${match[2]} || + print "format: $vdatefmt, string ${match[2]}" >&2 + schedrpt=$then + reply[text2]="${match[1]}${match[3]##[ ]#}" + ;; - (( changed = 0 )) - - # Look for specific warn time. - if [[ $reply[text2] = (#b)(|*[[:space:],])WARN[[:space:]](*) ]]; then + # Look for specific warn time. + ((#b)(|*[[:space:],])WARN[[:space:]](*)) if calendar_scandate -asm -R $reply[time] $match[2]; then reply[warntime]=$REPLY reply[warnstr]=${match[2]%%"$REPLY2"} - reply[text2]="${match[1]}${REPLY2##[[:space:]]#}" + # Remove spaces and tabs but not newlines from trailing text, + # else the formatting looks funny. + reply[text2]="${match[1]}${REPLY2##[ ]#}" else # Just remove the keyword for further parsing - reply[text2]="${match[1]}${match[2]##[[:space:]]#}" + reply[text2]="${match[1]}${match[2]##[ ]#}" fi - (( changed = 1 )) - elif [[ $reply[text2] = (#b)(|*[[:space:],])RPT[[:space:]](*) ]]; then - if calendar_scandate -a -R $reply[time] $match[2]; then - reply[rpttime]=$REPLY - reply[rptstr]=${match[2]%%"$REPLY2"} - reply[text2]="${match[1]}${REPLY2##[[:space:]]#}" - (( now = EPOCHSECONDS )) - while (( ${reply[rpttime]} < now )); do - # let's hope the original appointment wasn't in 44 B.C. - if calendar_scandate -a -R ${reply[rpttime]} ${reply[rptstr]}; then - if (( REPLY <= ${reply[rpttime]} )); then - # pathological case - break; - fi - reply[rpttime]=$REPLY - fi - done + ;; + + ((#b)(|*[[:space:],])RPT[[:space:]](*)) + before=${match[1]} + after=${match[2]} + if [[ $after = CANCELLED(|[[:space:]]*) ]]; then + reply[text2]="$before${match[2]##[ ]#}" + reply[rptstr]=CANCELLED + reply[rpttime]=CANCELLED + reply[schedrpttime]=CANCELLED + elif calendar_scandate -a -R $schedrpt $after; then + # It's possible to calculate a recurrence, however we don't + # do that yet. For now just keep the current time as + # the recurrence. Hence we ignore REPLY. + reply[text2]="$before${REPLY2##[ ]#}" + reply[rptstr]=${after%%"$REPLY2"} + # Until we find an individual occurrence, the actual time + # of the event is the regular one. + reply[rpttime]=$schedrpt else # Just remove the keyword for further parsing - reply[text2]="${match[1]}${match[2]##[[:space:]]#}" + reply[text2]="$before${after##[[:space:]]#}" fi - (( changed = 1 )) - fi + ;; + + ((#b)(|*[[:space:]\#])OCCURRENCE[[:space:]]##([^[:space:]]##)[[:space:]]##([^[:space:]]##)(*)) + occurrences[${match[2]}]="${match[3]}" + # as above + reply[text2]="${match[1]}${match[4]##[ ]#}" + ;; + + (*) + break + ;; + esac done +if [[ -n ${reply[rpttime]} && ${reply[rptstr]} != CANCELLED ]]; then + # Recurring event. We need to find out when it recurs. + (( now = EPOCHSECONDS )) + + # First find the next recurrence. + replaced=0 + reply[schedrpttime]=$schedrpt + if (( schedrpt >= now )); then + firstsched=$schedrpt + fi + while (( ${reply[schedrpttime]} < now || replaced )); do + if ! calendar_scandate -a -R ${reply[schedrpttime]} ${reply[rptstr]}; then + break + fi + if (( REPLY <= ${reply[schedrpttime]} )); then + # going backwards --- pathological case + break; + fi + reply[schedrpttime]=$REPLY + reply[rpttime]=$REPLY + if (( ${reply[schedrpttime]} > now && firstsched == 0 )); then + firstsched=$REPLY + fi + replaced=0 + # do we have an occurrence to compare against? + if (( ${#occurrences} )); then + strftime -s timefmt $vdatefmt ${reply[schedrpttime]} + occurrence=$occurrences[$timefmt] + if [[ -n $occurrence ]]; then + # Yes, this replaces the scheduled one. + replaced=1 + fi + fi + done + # Now look through occurrences (values only) and see which are (i) still + # to happen (ii) early than the current rpttime. + for occurrence in $occurrences; do + if [[ $occurrence != CANCELLED ]]; then + strftime -rs then $vdatefmt $occurrence || + print "format: $vdatefmt, string $occurrence" >&2 + if (( then > now && then < ${reply[rpttime]} )); then + reply[rpttime]=$then + fi + fi + done + # Finally, update the scheduled repeat time to the earliest + # possible value. This is so that if an occurrence replacement is + # cancelled we pick up the regular one. Can this happen? Dunno. + reply[schedrpttime]=$firstsched +fi + reply[text2]="${reply[text2]##[[:space:],]#}" return 0 -- cgit 1.4.1