From 483810a525b138f91dcb11f5864817a6e9ba6699 Mon Sep 17 00:00:00 2001 From: Peter Stephenson Date: Wed, 31 Jan 2007 16:53:31 +0000 Subject: 23142: calendar enhancements: relative times, recurring events --- Functions/Calendar/calendar_scandate | 167 ++++++++++++++++++++++++++++------- 1 file changed, 135 insertions(+), 32 deletions(-) (limited to 'Functions/Calendar/calendar_scandate') diff --git a/Functions/Calendar/calendar_scandate b/Functions/Calendar/calendar_scandate index 825aaf65b..53d0a9edf 100644 --- a/Functions/Calendar/calendar_scandate +++ b/Functions/Calendar/calendar_scandate @@ -46,7 +46,7 @@ # HH:MM.SS[.FFFFF] [am|pm|a.m.|p.m.] # in which square brackets indicate optional elements, possibly with # alternatives. Fractions of a second are recognised but ignored. -# Unless -r is given (see below), a date is mandatory but a time of day is +# Unless -r or -R are given (see below), a date is mandatory but a time of day is # not; the time returned is at the start of the date. # # Time zones are not handled, though if one is matched following a time @@ -122,10 +122,12 @@ # are optional, but are required between items, although a comma # may be used (with or without spaces). # -# Note that a year here is 365.25 days and a month is 30 days. TODO: -# improve this by passing down base time and adjusting. (This will -# be crucial for events repeating monthly.) TODO: it then makes -# sense to make PERIODly = 1 PERIOD (also for PERIOD = dai!) +# Note that a year here is 365.25 days and a month is 30 days. +# +# With -R start_time, a relative time is parsed and start_time is treated +# as the start of the period. This allows months and years to be calculated +# accurately. If the option -m (minus) is also given the relative time is +# taken backwards from the start time. # # This allows forms like: # 30 years 3 months 4 days 3:42:41 @@ -151,7 +153,9 @@ local tspat_noanchor="(|*${tschars})" # separator characters between elements. comma is fairly # natural punctuation; otherwise only allow whitespace. local schars="[.,[:space:]]" -local daypat="${schars}#(sun|mon|tue|wed|thu|fri|sat)[a-z]#${schars}#" +local -a dayarr +dayarr=(sun mon tue wed thu fri sat) +local daypat="${schars}#((#B)(${(j.|.)dayarr})[a-z]#~month*)" # Start pattern for date: treat , as space for simplicity. This # is illogical at the start but saves lots of minor fiddling later. # Date start pattern when anchored at the start. @@ -160,7 +164,7 @@ local daypat="${schars}#(sun|mon|tue|wed|thu|fri|sat)[a-z]#${schars}#" # (The problem in the other case is that matching anything before # the day of the week is greedy, so the day of the week gets ignored # if it's optional.) -local dspat_anchor="(|(#B)${daypat}(#b)${schars}#)" +local dspat_anchor="(|(#B)(${daypat}|)(#b)${schars}#)" local dspat_anchor_noday="(|${schars}#)" # Date start pattern when not anchored at the start. local dspat_noanchor="(|*${schars})" @@ -170,10 +174,10 @@ local repat="(|s)(|${schars}*)" # of the system for the purpose of finding out where they occur. # We may need some completely different heuristic. local monthpat="(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]#" -# days, not handled but we need to ignore them. also not localized. +integer daysecs=$(( 24 * 60 * 60 )) -integer year month day hour minute second then -local opt line orig_line mname MATCH MBEGIN MEND tz +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 -a match mbegin mend # Flags that we found a date or a time (maybe a relative time) integer date_found time_found @@ -183,9 +187,10 @@ integer time_ok # These are actual character indices as zsh would normally use, i.e. # line[time_start,time_end] is the string for the time. integer time_start time_end date_start date_end -integer anchor anchor_end debug relative reladd setvar +integer anchor anchor_end debug setvar +integer relative relative_start reladd reldate relsign=1 -while getopts "aAdrst" opt; do +while getopts "aAdmrR:st" opt; do case $opt in (a) # anchor @@ -202,10 +207,21 @@ while getopts "aAdrst" opt; do (( debug = 1 )) ;; + (m) + # relative with negative offsets + (( relsign = -1 )) + ;; + (r) + # relative with no fixed start (( relative = 1 )) ;; + (R) + # relative with fixed start supplied + (( relative_start = OPTARG, relative = 2 )) + ;; + (s) (( setvar = 1 )) ;; @@ -381,7 +397,7 @@ if (( relative == 0 )); then ;; # Look for WEEKDAY - ((#bi)${~dspat_noday}(${~daypat})*) + ((#bi)${~dspat_noday}(${~daypat})(|${~schars})*) integer wday_now wday local wdaystr=${(L)match[3]} date_start=$mbegin[2] date_end=$mend[2] @@ -405,15 +421,22 @@ if (( relative == 0 )); then ;; # Look for "today", "yesterday", "tomorrow" - ((#bi)${~dspat_noday}(yesterday|today|tomorrow)(|${schars})*) + ((#bi)${~dspat_noday}(yesterday|today|tomorrow|now)(|${~schars})*) (( then = EPOCHSECONDS )) case ${(L)match[2]} in (yesterday) - (( then -= 24 * 60 * 60 )) + (( then -= daysecs )) ;; (tomorrow) - (( then += 24 * 60 * 60 )) + (( then += daysecs )) + ;; + + (now) + time_found=1 time_end=0 time_start=1 + strftime -s hour "%H" $then + strftime -s minute "%M" $then + strftime -s second "%S" $then ;; esac strftime -s year "%Y" $then @@ -429,7 +452,7 @@ if (( date_found || (time_ok && time_found) )); then # date found # see if there's a day at the start if (( date_found )); then - if [[ ${line[1,$date_start-1]} = (#bi)${~daypat} ]]; then + if [[ ${line[1,$date_start-1]} = (#bi)${~daypat}${~schars}# ]]; then date_start=$mbegin[1] fi line=${line[1,$date_start-1]}${line[$date_end+1,-1]} @@ -512,38 +535,117 @@ if (( date_found || (time_ok && time_found) )); then fi if (( relative )); then - if [[ $line = (#bi)${~dspat}(<->)[[:blank:]]#(y|yr|year)${~repat} ]]; then - (( reladd += ((365*4+1) * 24 * 60 * 60 * ${match[2]} + 1) / 4 )) + if (( relative == 2 )); then + # Relative years and months are variable, and we may need to + # be careful about days. + strftime -s year "%Y" $relative_start + strftime -s month "%m" $relative_start + strftime -s day "%d" $relative_start + strftime -rs then "%Y:%m:%d" "${year}:${month}:${day}" + fi + if [[ $line = (#bi)${~dspat}(<->|)[[:space:]]#(y|yr|year|yearly)${~repat} ]]; then + [[ -z $match[2] ]] && match[2]=1 + if (( relative == 2 )); then + # We need the difference between relative_start & the + # time ${match[2]} years later. This means keeping the month and + # day the same and changing the year. + (( year2 = year + relsign * ${match[2]} )) + strftime -rs reldate "%Y:%m:%d" "${year2}:${month}:${day}" + + # If we've gone from a leap year to a non-leap year, go back a day. + strftime -s month2 "%m" $reldate + (( month2 != month )) && (( reldate -= daysecs )) + + # Keep this as a difference for now since we may need to add in other stuff. + (( reladd += reldate - then )) + else + (( reladd += relsign * ((365*4+1) * daysecs * ${match[2]} + 1) / 4 )) + fi line=${line[1,$mbegin[2]-1]}${line[$mend[4]+1,-1]} time_found=1 fi - if [[ $line = (#bi)${~dspat}(<->)[[:blank:]]#(mth|mon|mnth|month)${~repat} ]]; then - (( reladd += 30 * 24 * 60 * 60 * ${match[2]} )) + if [[ $line = (#bi)${~dspat}(<->|)[[:space:]]#(mth|mon|mnth|month|monthly)${~repat} ]]; then + [[ -z $match[2] ]] && match[2]=1 + if (( relative == 2 )); then + # Need to add on ${match[2]} months as above. + (( month2 = month + relsign * ${match[2]} )) + if (( month2 <= 0 )); then + # going backwards beyond start of given year + (( year2 = year + month2 / 12 - 1, month2 = month2 + (year-year2)*12 )) + else + (( year2 = year + (month2 - 1)/ 12, month2 = (month2 - 1) % 12 + 1 )) + fi + strftime -rs reldate "%Y:%m:%d" "${year2}:${month2}:${day}" + + # If we've gone past the end of the month because it was too short, + # we have two options (i) get the damn calendar fixed (ii) wind + # back to the end of the previous month. (ii) is easier for now. + if (( day > 28 )); then + while true; do + strftime -s day2 "%d" $reldate + # There are only up to 3 days in it, so just wind back one at a time. + # Saves counting. + (( day2 >= 28 )) && break + (( reldate -= daysecs )) + done + fi + + (( reladd += reldate - then )) + else + (( reladd += relsign * 30 * daysecs * ${match[2]} )) + fi line=${line[1,$mbegin[2]-1]}${line[$mend[4]+1,-1]} time_found=1 fi - if [[ $line = (#bi)${~dspat}(<->)[[:blank:]]#(w|wk|week)${~repat} ]]; then - (( reladd += 7 * 24 * 60 * 60 * ${match[2]} )) + if [[ $relative = 2 && $line = (#bi)${~dspat_noday}(<->)(th|rd|st)(${~daypat})(|${~schars}*) ]]; then + nth=$match[2] + test=${(L)${${match[4]##${~schars}#}%%${~schars}#}[1,3]} + wday=${dayarr[(I)$test]} + if (( wday )); then + line=${line[1,$mbegin[2]-1]}${line[$mend[4]+1,-1]} + time_found=1 + # We want weekday 0 to 6 + (( wday-- )) + (( reldate = relative_start + reladd )) + strftime -s year2 "%Y" $reldate + strftime -s month2 "%m" $reldate + # Find day of week of the first of the month we've landed on. + strftime -rs then "%Y:%m:%d" "${year2}:${month2}:1" + strftime -s wday2 "%w" $then + # Calculate day of month + (( day = 1 + (wday - wday2) + (nth - 1) * 7 )) + (( wday < wday2 )) && (( day += 7 )) + # whereas the day of the month calculated so far is... + strftime -s day2 "%d" $reldate + # so we need to compensate by... + (( reladd += (day - day2) * daysecs )) + fi + fi + if [[ $line = (#bi)${~dspat}(<->|)[[:space:]]#(w|wk|week|weekly)${~repat} ]]; then + [[ -z $match[2] ]] && match[2]=1 + (( reladd += relsign * 7 * daysecs * ${match[2]} )) line=${line[1,$mbegin[2]-1]}${line[$mend[4]+1,-1]} time_found=1 fi - if [[ $line = (#bi)${~dspat}(<->)[[:blank:]]#(d|dy|day)${~repat} ]]; then - (( reladd += 24 * 60 * 60 * ${match[2]} )) + if [[ $line = (#bi)${~dspat}(<->|)[[:space:]]#(d|dy|day|daily)${~repat} ]]; then + [[ -z $match[2] ]] && match[2]=1 + (( reladd += relsign * daysecs * ${match[2]} )) line=${line[1,$mbegin[2]-1]}${line[$mend[4]+1,-1]} time_found=1 fi - if [[ $line = (#bi)${~dspat}(<->)[[:blank:]]#(h|hr|hour)${~repat} ]]; then - (( reladd += 60 * 60 * ${match[2]} )) + if [[ $line = (#bi)${~dspat}(<->|)[[:space:]]#(h|hr|hour|hourly)${~repat} ]]; then + [[ -z $match[2] ]] && match[2]=1 + (( reladd += relsign * 60 * 60 * ${match[2]} )) line=${line[1,$mbegin[2]-1]}${line[$mend[4]+1,-1]} time_found=1 fi - if [[ $line = (#bi)${~dspat}(<->)[[:blank:]]#(min|minute)${~repat} ]]; then - (( reladd += 60 * ${match[2]} )) + if [[ $line = (#bi)${~dspat}(<->)[[:space:]]#(min|minute)${~repat} ]]; then + (( reladd += relsign * 60 * ${match[2]} )) line=${line[1,$mbegin[2]-1]}${line[$mend[4]+1,-1]} time_found=1 fi - if [[ $line = (#bi)${~dspat}(<->)[[:blank:]]#(s|sec|second)${~repat} ]]; then - (( reladd += ${match[2]} )) + if [[ $line = (#bi)${~dspat}(<->)[[:space:]]#(s|sec|second)${~repat} ]]; then + (( reladd += relsign * ${match[2]} )) line=${line[1,$mbegin[2]-1]}${line[$mend[4]+1,-1]} time_found=1 fi @@ -558,7 +660,8 @@ if (( relative )); then return 1 fi fi - (( REPLY = reladd + (hour * 60 + minute) * 60 + second )) + # relative_start is zero if we're not using it + (( REPLY = relative_start + reladd + (hour * 60 + minute) * 60 + second )) [[ -n $setvar ]] && REPLY2=$line return 0 fi -- cgit 1.4.1