about summary refs log tree commit diff
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.tar.xz
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