summaryrefslogtreecommitdiff
path: root/Functions/Calendar/calendar_scandate
diff options
context:
space:
mode:
Diffstat (limited to 'Functions/Calendar/calendar_scandate')
-rw-r--r--Functions/Calendar/calendar_scandate167
1 files changed, 135 insertions, 32 deletions
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