about summary refs log tree commit diff
path: root/Functions/Calendar
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
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')
-rw-r--r--Functions/Calendar/calendar6
-rw-r--r--Functions/Calendar/calendar_add178
-rw-r--r--Functions/Calendar/calendar_parse155
-rw-r--r--Functions/Calendar/calendar_scandate124
4 files changed, 340 insertions, 123 deletions
diff --git a/Functions/Calendar/calendar b/Functions/Calendar/calendar
index e4cdff8e4..48876aa51 100644
--- a/Functions/Calendar/calendar
+++ b/Functions/Calendar/calendar
@@ -296,7 +296,9 @@ chmod 600 $mycmds
     fi
     # Look for a repeat time.
     if [[ -n ${reply[rpttime]} ]]; then
-      (( repeattime = ${reply[rpttime]}, repeating = 1 ))
+      # The actual time of the next event, which appears as text
+      (( repeattime = ${reply[rpttime]} ))
+      (( repeating = 1 ))
     else
       (( repeating = 0 ))
     fi
@@ -320,7 +322,7 @@ chmod 600 $mycmds
     match=()
     # Strip continuation lines starting " #".
     while [[ $showline = (#b)(*$'\n')[[:space:]]##\#[^$'\n']##(|$'\n'(*)) ]]; do
-	  showline="$match[1]$match[3]"
+      showline="$match[1]$match[3]"
     done
     # Strip trailing empty lines
     showline=${showline%%[[:space:]]#}
diff --git a/Functions/Calendar/calendar_add b/Functions/Calendar/calendar_add
index eded25b2a..c06deda3a 100644
--- a/Functions/Calendar/calendar_add
+++ b/Functions/Calendar/calendar_add
@@ -7,14 +7,19 @@
 # entry before the first existing entry with a later date and time.
 
 emulate -L zsh
-setopt extendedglob
+setopt extendedglob # xtrace
 
 local context=":datetime:calendar_add:"
+local vdatefmt="%Y%m%dT%H%M%S"
+local d='[[:digit:]]'
 
-local calendar newfile REPLY lastline opt
-local -a calendar_entries lockfiles reply
-integer my_date done rstat nolock nobackup new_recurring old_recurring
-local -A reply parse_new parse_old recurring_uids
+local calendar newfile REPLY lastline opt text occur
+local -a calendar_entries lockfiles reply occurrences
+integer my_date done rstat nolock nobackup new_recurring
+integer keep_my_uid
+local -A reply parse_new parse_old
+local -a match mbegin mend
+local my_uid their_uid
 
 autoload -U calendar_{parse,read,lockfiles}
 
@@ -47,7 +52,6 @@ if ! calendar_parse $addline; then
 fi
 parse_new=("${(@kv)reply}")
 (( my_date = $parse_new[time] ))
-[[ -n $parse_new[rpttime] ]] && (( new_recurring = 1 ))
 if zstyle -t $context reformat-date; then
   local datefmt
   zstyle -s $context date-format datefmt ||
@@ -55,12 +59,24 @@ if zstyle -t $context reformat-date; then
   strftime -s REPLY $datefmt $parse_new[time]
   addline="$REPLY $parse_new[text1]"
 fi
+if [[ -n $parse_new[rptstr] ]]; then
+  (( new_recurring = 1 ))
+  if [[ $parse_new[rptstr] = CANCELLED ]]; then
+    (( done = 1 ))
+  elif [[ $addline = (#b)(*[[:space:]\#]RECURRENCE[[:space:]]##)([^[:space:]]##)([[:space:]]*|) ]]; then
+    # Use the updated recurrence time
+    strftime -s REPLY $vdatefmt ${parse_new[schedrpttime]}
+    addline="${match[1]}$REPLY${match[3]}"
+  else
+    # Add a recurrence time
+    [[ $addline = *$'\n' ]] || addline+=$'\n'
+    strftime -s REPLY $vdatefmt ${parse_new[schedrpttime]}
+    addline+="  # RECURRENCE $REPLY"
+  fi
+fi
 
 # $calendar doesn't necessarily exist yet.
 
-local -a match mbegin mend
-local my_uid their_uid
-
 # Match a UID, a unique identifier for the entry inherited from
 # text/calendar format.
 local uidpat='(|*[[:space:]])UID[[:space:]]##(#b)([[:xdigit:]]##)(|[[:space:]]*)'
@@ -87,14 +103,112 @@ fi
     calendar_read $calendar
 
     if [[ -n $my_uid ]]; then
-      # Pre-scan to find recurring events with a UID
+      # Pre-scan to events with the same UID
       for line in $calendar_entries; do
 	calendar_parse $line  ||  continue
+	parse_old=("${(@kv)reply}")
 	# Recurring with a UID?
-	if [[ -n $reply[rpttime] && $line = ${~uidpat} ]]; then
-	  # Yes, so record this as a recurring event.
+	if [[ $line = ${~uidpat} ]]; then
 	  their_uid=${(U)match[1]}
-	  recurring_uids[$their_uid]=$reply[time]
+	  if [[ $their_uid = $my_uid ]]; then
+	    # Deal with recurrences and also some add some
+	    # extra magic for cancellation.
+
+	    # See if we have found a recurrent version
+	    if [[ -z $parse_old[rpttime] ]]; then
+	      # No, so assume this is a straightforward replacement
+	      # of a non-recurring event.
+
+	      # Is this a cancellation of a non-recurring event?
+	      # Look for an OCCURRENCE in the form
+	      #   OCCURRENCE 20100513T110000 CANCELLED
+	      # although we don't bother looking at the date/time---
+	      # it's one-off, so this should already be unique.
+	      if [[ $new_recurring -eq 0 && \
+		$parse_new[text1] = (|*[[:space:]\#])"OCCURRENCE"[[:space:]]##([^[:space:]]##[[:space:]]##CANCELLED)(|[[:space:]]*) ]]; then
+		# Yes, so skip both the old and new events.
+		(( done = 1 ))
+	      fi
+	      # We'll skip this UID when we encounter it again.
+	      continue
+	    fi
+	    if (( new_recurring )); then
+	      # Replacing a recurrence; there can be only one.
+	      # TBD: do we replace existing occurrences of the
+	      # replaced recurrent event?  I'm guessing not, but
+	      # if we keep the UID then maybe we should.
+	      #
+	      # TBD: ick, suppose we're cancelling an even that
+	      # we added to a recurring sequence but didn't replace
+	      # the recurrence.  We might get RPT CANCELLED for this.
+	      # That would be bad.  Should invent better way of
+	      # cancelling non-recurring event.
+	      continue
+	    else
+	      # The recorded event is recurring, but the new one is a
+	      # one-off event. If there are embedded OCCURRENCE declarations,
+	      # use those.
+	      #
+	      # TBD: We could be clever about text associated
+	      # with the occurrence.  Copying the entire text
+	      # of the meeting seems like overkill but people often
+	      # add specific remarks about why this occurrence was
+	      # moved/cancelled.
+	      #
+	      # TBD: one case we don't yet handle is cancelling
+	      # something that isn't replacing a recurrence, i.e.
+	      # something we added as OCCURRENCE XXXXXXXXTXXXXXX <when>.
+	      # If we're adding a CANCELLED occurrence we should
+	      # look to see if it matches <when> and if so simply
+	      # delete that occurrence.
+	      #
+	      # TBD: one nasty case is if the new occurrence
+	      # occurs before the current scheduled time.  As we
+	      # never look backwards we'll miss it.
+	      text=$addline
+	      occurrences=()
+	      while [[ $text = (#b)(|*[[:space:]\#])"OCCURRENCE"[[:space:]]##([^[:space:]]##[[:space:]]##[^[:space:]]##)(|[[:space:]]*) ]]; do
+		occurrences+=($match[2])
+		text="$match[1] $match[3]"
+	      done
+	      if (( ! ${#occurrences} )); then
+		# No embedded occurrences.  We'll manufacture one
+		# that doesn't refer to an original recurrence.
+		strftime -s REPLY $vdatefmt $my_date
+		occurrences=("XXXXXXXXTXXXXXX $REPLY")
+	      fi
+	      # Add these occurrences, checking if they replace
+	      # an existing one.
+	      for occur in ${(o)occurrences}; do
+		REPLY=${occur%%[[:space:]]*}
+		# Only update occurrences that refer to genuine
+		# recurrences.
+		if [[ $REPLY = [[:digit:]](#c8)T[[:digit:]](#c6) && $line = (#b)(|*[[:space:]\#])(OCCURRENCE[[:space:]]##)${REPLY}[[:space:]]##[^[:space:]]##(|[[:space:]]*) ]]; then
+		  # Yes, update in situ
+		  line="${match[1]}${match[2]}$occur${match[3]}"
+		else
+		  # No, append.
+		  [[ $line = *$'\n' ]] || line+=$'\n'
+		  line+="  # OCCURRENCE $occur"
+		fi
+	      done
+	      # The line we're adding now corresponds to the
+	      # original event.  We'll skip the matching UID
+	      # in the file below, however.
+	      addline=$line
+	      # We need to work out which event is next, so
+	      # reparse.
+	      if calendar_parse $addline; then
+		parse_new=("${(@kv)reply}")
+		(( my_date = ${parse_new[time]} ))
+		if zstyle -t $context reformat-date; then
+		  zstyle -s $context date-format datefmt
+		  strftime -s REPLY $datefmt $parse_new[time]
+		  addline="$REPLY $parse_new[text1]"
+		fi
+	      fi
+	    fi
+	  fi
 	fi
       done
     fi
@@ -107,39 +221,11 @@ fi
 	  print -r -- $addline
 	  (( done = 1 ))
 	fi
-	if [[ -n $parse_old[rpttime] ]]; then
-	  (( old_recurring = 1 ))
-	else
-	  (( old_recurring = 0 ))
-	fi
-	if [[ -n $my_uid && $line = ${~uidpat} ]]; then
+	# We've already merged any information on the same UID
+	# with our new text, probably.
+	if [[ $keep_my_uid -eq 0 && -n $my_uid && $line = ${~uidpat} ]]; then
 	  their_uid=${(U)match[1]}
-	  if [[ $my_uid = $their_uid ]]; then
-	    # Deal with recurrences, being careful in case there
-	    # are one-off variants that don't replace recurrences.
-	    #
-	    # Bug 1: "calendar" still doesn't know about one-off variants.
-	    # Bug 2: neither do I; how do we know which occurrence
-	    # it replaces?
-	    # Bug 3: the code for calculating recurrences is awful anyway.
-
-	    if (( old_recurring && new_recurring )); then
-	      # Replacing a recurrence; there can be only one.
-	      continue
-	    elif (( ! new_recurring )); then
-	      # Not recurring.  See if we have previously found
-	      # a recurrent version
-	      [[ -n $recurring_uids[$their_uid] ]] && (( old_recurring = 1 ))
-	      # No, so assume this is a straightforward replacement
-	      # of a non-recurring event.
-	      (( ! old_recurring )) && continue
-	      # It's recurring, but if this is a one-off at the
-	      # same time as the previous one, replace anyway.
-	      [[ -z $parse_old[$rpttime] ]] &&
-	        (( ${parse_new[time]} == ${parse_old[time]} )) &&
-		continue
-	    fi
-	  fi
+	  [[ $my_uid = $their_uid ]] && continue
 	fi
 	if [[ $parse_old[time] -eq $my_date && $line = $addline ]]; then
 	  (( done )) && continue # paranoia: shouldn't happen
@@ -157,7 +243,7 @@ New calendar left in $newfile." >&2
       fi
     fi
   else
-    print -r -- $line >$newfile
+    (( done )) || print -r -- $addline >$newfile
   fi
 
   if (( !rstat )) && ! mv $newfile $calendar; then
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
diff --git a/Functions/Calendar/calendar_scandate b/Functions/Calendar/calendar_scandate
index 4ae2ae606..b3a583705 100644
--- a/Functions/Calendar/calendar_scandate
+++ b/Functions/Calendar/calendar_scandate
@@ -23,6 +23,19 @@
 #   from 1900 to 2099 inclusive are matched.
 # - Although timezones are parsed (complicated formats may not be recognized),
 #   they are then ignored; no time adjustment is made.
+# - Embedding of times within dates (e.g. "Wed Jun 16 09:30:00 BST 2010")
+#   causes horrific problems because of the combination of the many
+#   possible date and time formats to match.  The approach taken
+#   here is to match the time, remove it, and see if the nearby text
+#   looks like a date.  The problem is that the time matched may not
+#   be that associated with the date, in which case the time will be
+#   ignored.  To minimise this, when the argument "-a" is given to
+#   anchor the date/time to the start of the line, we never look
+#   beyond a newline.  So if any date/time strings in the text
+#   are on separate lines the problem is avoided.
+# - If you feel sophisticated enough and wish to avoid any ambiguity,
+#   you can use RFC 2445 date/time strings, for example 20100601T170000.
+#   These are parsed in one go.
 #
 # The following give some obvious examples; users finding here
 # a format they like and not subject to vagaries of style may skip
@@ -136,7 +149,7 @@
 # In this case absolute dates are ignored.
 
 emulate -L zsh
-setopt extendedglob
+setopt extendedglob # xtrace
 
 zmodload -i zsh/datetime || return 1
 
@@ -145,7 +158,7 @@ zmodload -i zsh/datetime || return 1
 # relatively logical dates like 2006/09/19:14:27
 # don't allow / before time !  the above
 # is not 19 hours 14 mins and 27 seconds after anything.
-local tschars="[-,:[:space:]]"
+local tschars="[-,:[:blank:]]"
 # start pattern for time when anchored
 local tspat_anchor="(${tschars}#)"
 # ... when not anchored
@@ -175,9 +188,10 @@ local repat="(|s)(|${schars}*)"
 # We may need some completely different heuristic.
 local monthpat="(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]#"
 integer daysecs=$(( 24 * 60 * 60 ))
+local d="[[:digit:]]"
 
 integer year year2 month month2 day day2 hour minute second then nth wday wday2
-local opt line orig_line mname MATCH MBEGIN MEND tz test
+local opt line orig_line mname MATCH MBEGIN MEND tz test rest_line
 local -a match mbegin mend
 # Flags that we found a date or a time (maybe a relative time)
 integer date_found time_found
@@ -237,7 +251,7 @@ while getopts "aAdmrR:st" opt; do
 done
 shift $(( OPTIND - 1 ))
 
-line=$1 orig_line=$1
+line=$1
 
 local dspat dspat_noday tspat
 if (( anchor )); then
@@ -250,11 +264,20 @@ if (( anchor )); then
     # We'll test later if the time is associated with the date.
     tspat=$tspat_noanchor
   fi
+  # We can save a huge amount of grief (I've discovered) if when
+  # we're anchored to the start we ignore anything after a newline.
+  # However, don't do this if we're anchored to the end.  The
+  # match should fail if there are extra lines in that case.
+  if [[ anchor_end -eq 0 && $line = (#b)([^$'\n']##)($'\n'*) ]]; then
+    line=$match[1]
+    rest_line=$match[2]
+  fi
 else
   dspat=$dspat_noanchor
   dspat_noday=$dspat_noanchor
   tspat=$tspat_noanchor
 fi
+orig_line=$line
 
 # Look for a time separately; we need colons for this.
 # We want to look for the first time to ensure it's associated
@@ -268,6 +291,7 @@ fi
 # To use a case statement we'd need to be able to request non-greedy
 # matching for a pattern.
 local rest
+# HH:MM:SECONDS am/pm with optional decimal seconds
 rest=${line#(#ibm)${~tspat}(<0-12>):(<0-59>)[.:]((<0-59>)(.<->|))[[:space:]]#([ap])(|.)[[:space:]]#m(.|[[:space:]]|(#e))}
 if [[ $rest != $line ]]; then
   hour=$match[2]
@@ -275,7 +299,8 @@ if [[ $rest != $line ]]; then
   second=$match[5]
   [[ $match[7] = (#i)p ]] && (( hour <= 12 )) && (( hour += 12 ))
   time_found=1
-else
+fi
+if (( time_found == 0 )); then
   # no seconds, am/pm
   rest=${line#(#ibm)${~tspat}(<0-12>):(<0-59>)[[:space:]]#([ap])(|.)[[:space:]]#m(.|[[:space:]]|(#e))}
   if [[ $rest != $line ]]; then
@@ -283,37 +308,60 @@ else
     minute=$match[3]
     [[ $match[4] = (#i)p ]] && (( hour <= 12 )) && (( hour += 12 ))
     time_found=1
-  else
-    # no colon, even, but a.m./p.m. indicator
-    rest=${line#(#ibm)${~tspat}(<0-12>)[[:space:]]#([ap])(|.)[[:space:]]#m(.|[[:space:]]|(#e))}
-    if [[ $rest != $line ]]; then
-      hour=$match[2]
-      minute=0
-      [[ $match[3] = (#i)p ]] && (( hour <= 12 )) && (( hour += 12 ))
-      time_found=1
-    else
-      # 24 hour clock, with seconds
-      rest=${line#(#ibm)${~tspat}(<0-24>):(<0-59>)[.:]((<0-59>)(.<->|))(.|[[:space:]]|(#e))}
-      if [[ $rest != $line ]]; then
-	hour=$match[2]
-	minute=$match[3]
-	second=$match[5]
-	time_found=1
-      else
-	rest=${line#(#ibm)${~tspat}(<0-24>):(<0-59>)(.|[[:space:]]|(#e))}
-	if [[ $rest != $line ]]; then
-	  hour=$match[2]
-	  minute=$match[3]
-	  time_found=1
-	fi
-      fi
-    fi
+  fi
+fi
+if (( time_found == 0 )); then
+  # no colon, even, but a.m./p.m. indicator
+  rest=${line#(#ibm)${~tspat}(<0-12>)[[:space:]]#([ap])(|.)[[:space:]]#m(.|[[:space:]]|(#e))}
+  if [[ $rest != $line ]]; then
+    hour=$match[2]
+    minute=0
+    [[ $match[3] = (#i)p ]] && (( hour <= 12 )) && (( hour += 12 ))
+    time_found=1
+  fi
+fi
+if (( time_found == 0 )); then
+  # 24 hour clock, with seconds
+  rest=${line#(#ibm)${~tspat}(<0-24>):(<0-59>)[.:]((<0-59>)(.<->|))(.|[[:space:]]|(#e))}
+  if [[ $rest != $line ]]; then
+    hour=$match[2]
+    minute=$match[3]
+    second=$match[5]
+    time_found=1
+  fi
+fi
+if (( time_found == 0 )); then
+  rest=${line#(#ibm)${~tspat}(<0-24>):(<0-59>)(.|[[:space:]]|(#e))}
+  if [[ $rest != $line ]]; then
+    hour=$match[2]
+    minute=$match[3]
+    time_found=1
+  fi
+fi
+if (( time_found == 0 )); then
+  # Combined date and time formats:  here we can use an anchor because
+  # we know the complete format.
+  (( anchor )) && tspat=$tspat_anchor
+  # RFC 2445
+  rest=${line#(#ibm)${~tspat}(|\"[^\"]##\":)($~d$~d$~d$~d)($~d$~d)($~d$~d)T($~d$~d)($~d$~d)($~d$~d)([[:space:]]#|(#e))}
+  if [[ $rest != $line ]]; then
+    year=$match[3]
+    month=$match[4]
+    day=$match[5]
+    hour=$match[6]
+    minute=$match[7]
+    second=$match[8]
+    # signal don't need to take account of time in date...
+    time_found=2
+    date_found=1
+    date_start=$mbegin[3]
+    date_end=$mend[-1]
   fi
 fi
 (( hour == 24 )) && hour=0
 
-if (( time_found )); then
-  # time was found
+if (( time_found && ! date_found )); then
+  # time was found; if data also found already, process below.
   time_start=$mbegin[2]
   time_end=$mend[-1]
   # Remove the timespec because it may be in the middle of
@@ -331,7 +379,7 @@ if (( time_found )); then
   (( debug )) && print "line after time: $line"
 fi
 
-if (( relative == 0 )); then
+if (( relative == 0 && date_found == 0 )); then
   # Date.
   case $line in
   # Look for YEAR[-/.]MONTH[-/.]DAY
@@ -468,7 +516,7 @@ if (( date_found || (time_ok && time_found) )); then
     fi
     line=${line[1,$date_start-1]}${line[$date_end+1,-1]}
   fi
-  if (( time_found )); then
+  if (( time_found == 1 )); then
     if (( date_found )); then
       # If we found a time, it must be associated with the date,
       # or we can't use it.  Since we removed the time from the
@@ -540,7 +588,7 @@ if (( date_found || (time_ok && time_found) )); then
 	"'$orig_line[time_start,time_end]'"
       (( date_ok )) && print "Date string: $date_start,$date_end:" \
 	"'$orig_line[date_start,date_end]'"
-      print "Remaining line: '$line'"
+      print "Remaining line: '$line$rest_line'"
     fi
   fi
 fi
@@ -722,11 +770,11 @@ if (( relative )); then
     (( reladd += (hour * 60 + minute) * 60 + second ))
     typeset -g REPLY
     (( REPLY = relative_start + reladd  ))
-    [[ -n $setvar ]] && typeset -g REPLY2="$line"
+    [[ -n $setvar ]] && typeset -g REPLY2="$line$rest_line"
     return 0
   fi
   return 1
-elif (( ! date_found )); then
+elif (( date_found == 0 )); then
   return 1
 fi
 
@@ -748,6 +796,6 @@ fi
 
 strftime -s REPLY -r $fmt $nums
 
-[[ -n $setvar ]] && typeset -g REPLY2="$line"
+[[ -n $setvar ]] && typeset -g REPLY2="$line$rest_line"
 
 return 0