summaryrefslogtreecommitdiff
path: root/Functions/Calendar/calendar_parse
diff options
context:
space:
mode:
authorPeter Stephenson <pws@users.sourceforge.net>2010-06-14 13:01:41 +0000
committerPeter Stephenson <pws@users.sourceforge.net>2010-06-14 13:01:41 +0000
commit14dde084755a8b15004d59bb6be5cc7a3726a8bf (patch)
tree067f4ebff5e399fb560c710b798a4e3421f771ea /Functions/Calendar/calendar_parse
parent4c1a3a89f0ade5be2330ce688cd3c3c649667f9a (diff)
downloadzsh-14dde084755a8b15004d59bb6be5cc7a3726a8bf.tar.gz
zsh-14dde084755a8b15004d59bb6be5cc7a3726a8bf.zip
28038: improved handling of recurring events in calendar system
Diffstat (limited to 'Functions/Calendar/calendar_parse')
-rw-r--r--Functions/Calendar/calendar_parse155
1 files changed, 118 insertions, 37 deletions
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 <timestamp> <disposition>"
+# may occur multiple times. We set occurrences[<timestamp>]=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