branch: master commit d0f7e21f9a20407d2032e1871bcc68be850c12f4 Author: Ian Dunn <du...@gnu.org> Commit: Ian Dunn <du...@gnu.org>
Added two new forms for setting planning information * org-edna.el (org-edna--read-date-get-relative): New defun for landing form. (org-edna--float-time): New defun for float form. (org-edna--handle-planning): Use the new functions. * org-edna-tests.el: Add new tests for landing and float. * org-edna.org (Scheduled/Deadline): Document the new forms. * org-edna.info: Updated documentation. --- org-edna-tests.el | 96 +++++++++++++++- org-edna.el | 324 +++++++++++++++++++++++++++++++++++++++++++++++++++--- org-edna.info | 186 +++++++++++++++++++------------ org-edna.org | 52 ++++++++- 4 files changed, 566 insertions(+), 92 deletions(-) diff --git a/org-edna-tests.el b/org-edna-tests.el index 00a9a8d..52d1540 100644 --- a/org-edna-tests.el +++ b/org-edna-tests.el @@ -352,7 +352,6 @@ (should (not (org-entry-get nil "SCHEDULED")))))) (ert-deftest org-edna-action-scheduled/cp () - ;; Override `current-time' so we can get a deterministic value (let* ((org-agenda-files `(,org-edna-test-file)) (target (org-id-find "0d491588-7da3-43c5-b51a-87fbd34f79f7" t)) (source (org-id-find "97e6b0f0-40c4-464f-b760-6e5ca9744eb5" t)) @@ -372,20 +371,113 @@ (org-agenda-files `(,org-edna-test-file)) (target (org-id-find "97e6b0f0-40c4-464f-b760-6e5ca9744eb5" t))) (org-with-point-at target - ;; Time started at Jan 15, 2000 + ;; Time starts at Jan 15, 2000 + (org-edna-action/scheduled! nil "2000-01-15 Sat 00:00") + (should (string-equal (org-entry-get nil "SCHEDULED") + "<2000-01-15 Sat 00:00>")) ;; Increment 1 minute (org-edna-action/scheduled! nil "+1M") (should (string-equal (org-entry-get nil "SCHEDULED") "<2000-01-15 Sat 00:01>")) + ;; Decrement 1 minute (org-edna-action/scheduled! nil "-1M") (should (string-equal (org-entry-get nil "SCHEDULED") "<2000-01-15 Sat 00:00>")) + ;; +1 day (org-edna-action/scheduled! nil "+1d") (should (string-equal (org-entry-get nil "SCHEDULED") "<2000-01-16 Sun 00:00>")) + ;; +1 hour from current time (org-edna-action/scheduled! nil "++1h") (should (string-equal (org-entry-get nil "SCHEDULED") "<2000-01-15 Sat 01:00>")) + ;; Back to Saturday + (org-edna-action/scheduled! nil "2000-01-15 Sat 00:00") + (should (string-equal (org-entry-get nil "SCHEDULED") + "<2000-01-15 Sat 00:00>")) + ;; -1 day to Friday + (org-edna-action/scheduled! nil "-1d") + (should (string-equal (org-entry-get nil "SCHEDULED") + "<2000-01-14 Fri 00:00>")) + ;; Increment two days to the next weekday + (org-edna-action/scheduled! nil "+2wkdy") + (should (string-equal (org-entry-get nil "SCHEDULED") + "<2000-01-17 Mon 00:00>")) + ;; Increment one day, expected to land on a weekday + (org-edna-action/scheduled! nil "+1wkdy") + (should (string-equal (org-entry-get nil "SCHEDULED") + "<2000-01-18 Tue 00:00>")) + ;; Move forward 8 days, then backward until we find a weekend + (org-edna-action/scheduled! nil "+8d -wknd") + (should (string-equal (org-entry-get nil "SCHEDULED") + "<2000-01-23 Sun 00:00>")) + ;; Move forward one week, then forward until we find a weekday + ;; (org-edna-action/scheduled! nil "+1w +wkdy") + ;; (should (string-equal (org-entry-get nil "SCHEDULED") + ;; "<2000-01-31 Mon 00:00>")) + ;; Back to Saturday for other tests + (org-edna-action/scheduled! nil "2000-01-15 Sat 00:00") + (should (string-equal (org-entry-get nil "SCHEDULED") + "<2000-01-15 Sat 00:00>"))))) + +(ert-deftest org-edna-action-scheduled/landing () + "Test landing arguments to scheduled increment." + ;; Override `current-time' so we can get a deterministic value + (cl-letf* (((symbol-function 'current-time) (lambda () org-edna-test-time)) + (org-agenda-files `(,org-edna-test-file)) + (target (org-id-find "97e6b0f0-40c4-464f-b760-6e5ca9744eb5" t))) + (org-with-point-at target + ;; Time starts at Jan 15, 2000 + (org-edna-action/scheduled! nil "2000-01-15 Sat 00:00") + (should (string-equal (org-entry-get nil "SCHEDULED") + "<2000-01-15 Sat 00:00>")) + ;; Move forward 10 days, then backward until we find a weekend + (org-edna-action/scheduled! nil "+10d -wknd") + (should (string-equal (org-entry-get nil "SCHEDULED") + "<2000-01-23 Sun 00:00>")) + ;; Move forward one week, then forward until we find a weekday + (org-edna-action/scheduled! nil "+1w +wkdy") + (should (string-equal (org-entry-get nil "SCHEDULED") + "<2000-01-31 Mon 00:00>")) + ;; Back to Saturday for other tests + (org-edna-action/scheduled! nil "2000-01-15 Sat 00:00") + (should (string-equal (org-entry-get nil "SCHEDULED") + "<2000-01-15 Sat 00:00>"))))) + +(ert-deftest org-edna-action-scheduled/float () + (cl-letf* (((symbol-function 'current-time) (lambda () org-edna-test-time)) + (org-agenda-files `(,org-edna-test-file)) + (target (org-id-find "97e6b0f0-40c4-464f-b760-6e5ca9744eb5" t))) + (org-with-point-at target + ;; Time starts at Jan 15, 2000 + (org-edna-action/scheduled! nil "2000-01-15 Sat 00:00") + (should (string-equal (org-entry-get nil "SCHEDULED") + "<2000-01-15 Sat 00:00>")) + ;; The third Tuesday of next month (Feb 15th) + (org-edna-action/scheduled! nil "float 3 Tue") + (should (string-equal (org-entry-get nil "SCHEDULED") + "<2000-02-15 Tue 00:00>")) + ;; The second Friday of the following May (May 12th) + (org-edna-action/scheduled! nil "float 2 5 May") + (should (string-equal (org-entry-get nil "SCHEDULED") + "<2000-05-12 Fri 00:00>")) + ;; Move forward to the second Wednesday of the next month (June 14th) + (org-edna-action/scheduled! nil "float 2 Wednesday") + (should (string-equal (org-entry-get nil "SCHEDULED") + "<2000-06-14 Wed 00:00>")) + ;; Move forward to the first Thursday in the following Jan (Jan 4th, 2001) + (org-edna-action/scheduled! nil "float 1 4 Jan") + (should (string-equal (org-entry-get nil "SCHEDULED") + "<2001-01-04 Thu 00:00>")) + ;; The fourth Monday in Feb, 2000 (Feb 28th) + (org-edna-action/scheduled! nil "float ++4 monday") + (should (string-equal (org-entry-get nil "SCHEDULED") + "<2000-02-28 Mon 00:00>")) + ;; The second Monday after Mar 12th, 2000 (Mar 20th) + (org-edna-action/scheduled! nil "float 2 monday Mar 12") + (should (string-equal (org-entry-get nil "SCHEDULED") + "<2000-03-20 Mon 00:00>")) + ;; Back to Saturday for other tests (org-edna-action/scheduled! nil "2000-01-15 Sat 00:00") (should (string-equal (org-entry-get nil "SCHEDULED") "<2000-01-15 Sat 00:00>"))))) diff --git a/org-edna.el b/org-edna.el index c01d354..364aa8b 100644 --- a/org-edna.el +++ b/org-edna.el @@ -711,14 +711,269 @@ N is an integer. WHAT can be `day', `month', `year', `minute', WHAT is either 'scheduled or 'deadline." (org-entry-get nil (if (eq what 'scheduled) "SCHEDULED" "DEADLINE"))) +;; Silence the byte-compiler +(defvar parse-time-weekdays) +(defvar parse-time-months) + +(defun org-edna--read-date-get-relative (s today default) + "Like `org-read-date-get-relative' but with a few additions. + +S is a string with the form [+|-|++|--][N]THING. + +THING may be any of the following: + +- A weekday (WEEKDAY), in which case the number of days from + either TODAY or DEFAULT to the next WEEKDAY will be computed. + If N is given, jump forward that many occurrences of WEEKDAY + +- The string \"weekday\" or \"wkdy\", in which jump forward X + days to land on a weekday. If a weekend is found instead, move + in the direction given (+/-) until a weekday is found. + +S may also end with [+|-][DAY]. DAY may be either a weekday +string, such as Monday, Tue, or Friday, or the strings +\"weekday\", \"wkdy\", \"weekend\", or \"wknd\". The former +indicates that the time should land on the given day of the week, +while the latter group indicates that the time should land on +that type, either a weekday or a weekend. The [+|-] in this +string indicates that the time should be incremented or +decremented to find the target day. + +Return shift list (N what def-flag) to get to the desired date +WHAT is \"M\", \"h\", \"d\", \"w\", \"m\", or \"y\" for minute, hour, day, week, month, year. +N is the number of WHATs to shift. +DEF-FLAG is t when a double ++ or -- indicates shift relative to + the DEFAULT date rather than TODAY. + +Examples: + +\"+1d +wkdy\" finds the number of days to move ahead in order to +find a weekday. This is the same as \"+1wkdy\", and returns +\(N \"d\" nil). + +\"+5d -wkdy\" means move forward 5 days, then backward until a +weekday is found. Returns (N \"d\" nil). + +\"+1m +wknd\" means move forward one month, then forward until a +weekend is found. Returns (N \"d\" nil), since day precision is +required." + (require 'parse-time) + (let* ((case-fold-search t) ;; ignore case when matching, so we get any + ;; capitalization of weekday names + (weekdays (mapcar 'car parse-time-weekdays)) + ;; type-strings maps the type of thing to the index in decoded time + ;; (see `decode-time') + (type-strings '(("M" . 1) + ("h" . 2) + ("d" . 3) + ("w" . 3) + ("m" . 4) + ("y" . 5))) + (regexp (rx-to-string + `(and string-start + (submatch (repeat 0 2 (in ?+ ?-))) + (submatch (zero-or-more digit)) + (submatch (or (any ,@(mapcar 'car type-strings)) + "weekday" "wkdy" + ,@weekdays) + word-end) + (zero-or-one + (submatch (and (one-or-more " ") + (submatch (zero-or-one (in ?+ ?-))) + (submatch (or "weekday" "wkdy" + "weekend" "wknd" + ,@weekdays) + word-end)))) + string-end)))) + (when (string-match regexp s) + (let* ((dir (if (> (match-end 1) (match-beginning 1)) + (string-to-char (substring (match-string 1 s) -1)) + ?+)) + (rel (and (match-end 1) (= 2 (- (match-end 1) (match-beginning 1))))) + (n (if (match-end 2) (string-to-number (match-string 2 s)) 1)) + (what (if (match-end 3) (match-string 3 s) "d")) + (wday1 (cdr (assoc (downcase what) parse-time-weekdays))) + (date (if rel default today)) + (wday (nth 6 (decode-time date))) + ;; Are we worrying about where we land? + (have-landing (match-end 4)) + (landing-direction (string-to-char + (if (and have-landing (match-end 5)) + (match-string 5 s) + "+"))) + (landing-type (when have-landing (match-string 6 s))) + delta ret) + (setq + ret + (pcase what + ;; Shorthand for +Nd +wkdy or -Nd -wkdy + ((or "weekday" "wkdy") + ;; Determine where we land after N days + (let* ((del (* n (if (= dir ?-) -1 1))) + (end-day (mod (+ del wday) 7))) + (while (member end-day calendar-weekend-days) + (let ((d (if (= dir ?-) -1 1))) + (cl-incf del d) + (setq end-day (mod (+ end-day d) 7)))) + (list del "d" rel))) + ((pred (lambda (arg) (member arg (mapcar 'car type-strings)))) + (list (* n (if (= dir ?-) -1 1)) what rel)) + ((pred (lambda (arg) (member arg weekdays))) + (setq delta (mod (+ 7 (- wday1 wday)) 7)) + (when (= delta 0) (setq delta 7)) + (when (= dir ?-) + (setq delta (- delta 7)) + (when (= delta 0) (setq delta -7))) + (when (> n 1) (setq delta (+ delta (* (1- n) (if (= dir ?-) -7 7))))) + (list delta "d" rel)))) + (if (or (not have-landing) + (member what '("M" "h"))) ;; Don't change landing for minutes or hours + ret ;; Don't worry about landing, just return + (pcase-let* ((`(,del ,what _) ret) + (mod-index (cdr (assoc what type-strings))) + ;; Increment the appropriate entry in the original decoded time + (raw-landing-time + (let ((tmp (copy-sequence (decode-time date)))) + (cl-incf (seq-elt tmp mod-index) + ;; We increment the days by 7 when we have weeks + (if (string-equal what "w") (* 7 del) del)) + tmp)) + (encoded-landing-time (apply 'encode-time raw-landing-time)) + ;; Get the initial time difference in days, rounding down + ;; (it should be something like 3.0, so it won't matter) + (time-diff (truncate + (/ (float-time (time-subtract encoded-landing-time + date)) + 86400))) ;; seconds in a day + ;; Decoded landing time + (landing-time (decode-time encoded-landing-time)) + ;; Numeric Landing direction + (l-dir (if (= landing-direction ?-) -1 1)) + ;; Current numeric day of the week on which we end + (end-day (nth 6 landing-time)) + ;; Numeric days of the week on which we are allowed to land + (allowed-targets + (pcase landing-type + ((or "weekday" "wkdy") + (seq-difference (number-sequence 0 6) calendar-weekend-days)) + ((or "weekend" "wknd") + calendar-weekend-days) + ((pred (lambda (arg) (member arg weekdays))) + (list (cdr (assoc (downcase landing-type) parse-time-weekdays))))))) + ;; While we aren't looking at a valid day, move one day in the l-dir + ;; direction. + (while (not (member end-day allowed-targets)) + (cl-incf time-diff l-dir) + (setq end-day (mod (+ end-day l-dir) 7))) + (list time-diff "d" rel))))))) + +(defun org-edna--float-time (arg this-time default) + "Read a float time string from ARG. + +A float time argument string is as follows: + +float [+|-|++|--]?N DAYNAME[ MONTH[ DAY]] + +N is an integer +DAYNAME is either an integer day of the week, or a weekday string + +MONTH may be a month string or an integer. Use 0 for the +following or previous month. + +DAY is an optional integer. If not given, it will be 1 (for +forward) or the last day of MONTH (backward)." + (require 'parse-time) + (let* ((case-fold-search t) + (weekdays (mapcar 'car parse-time-weekdays)) + (month-names (mapcar 'car parse-time-months)) + (regexp (rx-to-string + `(and string-start + "float " + ;; First argument, N + (submatch (repeat 0 2 (in ?+ ?-))) + (submatch word-start (one-or-more digit) word-end) + " " + ;; Second argument, weekday digit or string + (submatch word-start + (or (in (?0 . ?6)) ;; Weekday digit + ,@weekdays) + word-end) + ;; Third argument, month digit or string + (zero-or-one + " " (submatch word-start + (or (repeat 1 2 digit) + ,@month-names) + word-end) + ;; Fourth argument, day in month + (zero-or-one + " " + (submatch word-start + (repeat 1 2 digit) + word-end))))))) + (when (string-match regexp arg) + (pcase-let* ((inc (match-string 1 arg)) + (dir (if (not (string-empty-p inc)) ;; non-empty string + (string-to-char (substring inc -1)) + ?+)) + (rel (= (length inc) 2)) + (numeric-dir (if (= dir ?+) 1 -1)) + (nth (* (string-to-number (match-string 2 arg)) numeric-dir)) + (dayname (let* ((tmp (match-string 3 arg)) + (day (cdr (assoc (downcase tmp) parse-time-weekdays)))) + (or day (string-to-number tmp)))) + (month (if-let* ((tmp (match-string 4 arg))) + (or (cdr (assoc (downcase tmp) parse-time-months)) + (string-to-number tmp)) + 0)) + (day (if (match-end 5) (string-to-number (match-string 5 arg)) 0)) + (ts (if rel default this-time)) + (`(_ _ _ ,dec-day ,dec-month ,dec-year _ _ _) (decode-time ts)) + ;; If month isn't given, use the 1st of the following (or previous) month + ;; If month is given, use the 1st (or day, if given) of that + ;; following month + (month-given (not (= month 0))) + ;; If day isn't provided, pass nil to + ;; `calendar-nth-named-absday' so it can handle it. + (act-day (if (not (= day 0)) day nil)) + (`(,act-month ,act-year) + (if (not month-given) + ;; Month wasn't given, so start at the following or previous month. + (list (+ dec-month (if (= dir ?+) 1 -1)) dec-year) + ;; Month was given, so adjust the year accordingly + (cond + ;; If month is after dec-month and we're incrementing, + ;; keep year + ((and (> month dec-month) (= dir ?+)) + (list month dec-year)) + ;; If month is before or the same as dec-month, and we're + ;; incrementing, increment year. + ((and (<= month dec-month) (= dir ?+)) + (list month (1+ dec-year))) + ;; We're moving backwards, but month is after, so + ;; decrement year. + ((and (>= month dec-month) (= dir ?-)) + (list month (1- dec-year))) + ;; We're moving backwards, and month is backward, so + ;; leave it. + ((and (< month dec-month) (= dir ?-)) + (list month dec-year))))) + (abs-days-now (calendar-absolute-from-gregorian `(,dec-month + ,dec-day + ,dec-year))) + (abs-days-then (calendar-nth-named-absday nth dayname + act-month + act-year + act-day))) + (message "act day = %s" act-day) + ;; Return the same arguments as `org-edna--read-date-get-relative' above. + (list (- abs-days-then abs-days-now) "d" rel))))) + (defun org-edna--handle-planning (type last-entry args) "Handle planning of type TYPE." - ;; Need case-fold-search enabled so org-read-date-get-relative will recognize "M" - (let* ((case-fold-search t) - (arg (nth 0 args)) + (let* ((arg (nth 0 args)) (last-ts (org-with-point-at last-entry (org-edna--get-planning-info type))) (this-ts (org-edna--get-planning-info type)) - (this-time (and this-ts (org-parse-time-string this-ts))) + (this-time (and this-ts (org-time-string-to-time this-ts))) (current (org-current-time)) (current-ts (format-time-string (org-time-stamp-format t) current)) (type-map '(("y" . year) @@ -734,11 +989,16 @@ WHAT is either 'scheduled or 'deadline." (error "Tried to copy but last entry doesn't have a timestamp")) ;; Copy old time verbatim (org-add-planning-info type last-ts)) + ((string-match-p "\\`float " arg) + (pcase-let* ((`(,n ,what-string ,def) (org-edna--float-time arg this-time current)) + (ts (if def current-ts this-ts)) + (what (cdr (assoc-string what-string type-map)))) + (org--deadline-or-schedule nil type (org-edna--mod-timestamp ts n what)))) ((string-match-p "\\`[+-]" arg) ;; Starts with a + or -, so assume we're incrementing a timestamp ;; We support hours and minutes, so this must be supported separately, ;; since org-read-date-analyze doesn't - (pcase-let* ((`(,n ,what-string ,def) (org-read-date-get-relative arg this-time current)) + (pcase-let* ((`(,n ,what-string ,def) (org-edna--read-date-get-relative arg this-time current)) (ts (if def current-ts this-ts)) (what (cdr (assoc-string what-string type-map)))) (org--deadline-or-schedule nil type (org-edna--mod-timestamp ts n what)))) @@ -758,10 +1018,11 @@ WHAT is either 'scheduled or 'deadline." (defun org-edna-action/scheduled! (last-entry &rest args) "Action to set the scheduled time of a target heading based on ARGS. -Edna Syntax: scheduled!(\"DATE[ TIME]\") [1] -Edna Syntax: scheduled!(rm|remove) [2] -Edna Syntax: scheduled!(cp|copy) [3] -Edna Syntax: scheduled!(\"[+|-|++|--]NTHING\") [4] +Edna Syntax: scheduled!(\"DATE[ TIME]\") [1] +Edna Syntax: scheduled!(rm|remove) [2] +Edna Syntax: scheduled!(cp|copy) [3] +Edna Syntax: scheduled!(\"[+|-|++|--]NTHING[ [+|-]LANDING]\") [4] +Edna Syntax: scheduled!(\"float [+|-|++|--]?N DAYNAME [ DAY[ MONTH]]\") [5] In form 1, schedule the target for the given date and time. If DATE is a weekday instead of a date, schedule the target for the @@ -778,16 +1039,33 @@ heading) to the target. Form 4 increments(+) or decrements(-) the target's scheduled time by N THINGS relative to either itself (+/-) or the current time (++/--). THING is one of y (years), m (months), d (days), -h (hours), or M (minutes), and N is an integer." +h (hours), or M (minutes), and N is an integer. + +Form 4 may also include a \"landing\" specification. This is +either (a) a day of the week (\"Sun\", \"friday\", etc.), (b) +\"weekday\" or \"wkdy\", or (c) \"weekend\" or \"wknd\". + +If (a), then the target date will be adjusted forward (+) or +backward (-) to find the closest target day of the week. +Form (b) will adjust the target time to find a weekday, and (c) +does the same, but for weekends. + +Form 5 handles \"float\" time, named for `diary-float'. This +form will set the target's scheduled time to the date of the Nth +DAYNAME after/before MONTH DAY. MONTH may be a month string or +an integer. Use 0 or leave blank for the following or previous +month. DAY is an optional integer. If not given, it will be +1 (for forward) or the last day of MONTH (backward)." (org-edna--handle-planning 'scheduled last-entry args)) (defun org-edna-action/deadline! (last-entry &rest args) "Action to set the deadline time of a target heading based on ARGS. -Edna Syntax: deadline!(\"DATE[ TIME]\") [1] -Edna Syntax: deadline!(rm|remove) [2] -Edna Syntax: deadline!(cp|copy) [3] -Edna Syntax: deadline!(\"[+|-|++|--]NTHING\") [4] +Edna Syntax: deadline!(\"DATE[ TIME]\") [1] +Edna Syntax: deadline!(rm|remove) [2] +Edna Syntax: deadline!(cp|copy) [3] +Edna Syntax: deadline!(\"[+|-|++|--]NTHING[ [+|-]LANDING]\") [4] +Edna Syntax: deadline!(\"float [+|-|++|--]?N DAYNAME [ DAY[ MONTH]]\") [5] In form 1, set the deadline the target for the given date and time. If DATE is a weekday instead of a date, set the deadline @@ -805,7 +1083,23 @@ heading) to the target. Form 4 increments(+) or decrements(-) the target's deadline time by N THINGS relative to either itself (+/-) or the current time (++/--). THING is one of y (years), m (months), d (days), -h (hours), or M (minutes), and N is an integer." +h (hours), or M (minutes), and N is an integer. + +Form 4 may also include a \"landing\" specification. This is +either (a) a day of the week (\"Sun\", \"friday\", etc.), (b) +\"weekday\" or \"wkdy\", or (c) \"weekend\" or \"wknd\". + +If (a), then the target date will be adjusted forward (+) or +backward (-) to find the closest target day of the week. +Form (b) will adjust the target time to find a weekday, and (c) +does the same, but for weekends. + +Form 5 handles \"float\" time, named for `diary-float'. This +form will set the target's scheduled time to the date of the Nth +DAYNAME after/before MONTH DAY. MONTH may be a month string or +an integer. Use 0 or leave blank for the following or previous +month. DAY is an optional integer. If not given, it will be +1 (for forward) or the last day of MONTH (backward)." (org-edna--handle-planning 'deadline last-entry args)) (defun org-edna-action/tag! (_last-entry tags) diff --git a/org-edna.info b/org-edna.info index 999d669..4bc75e0 100644 --- a/org-edna.info +++ b/org-edna.info @@ -73,9 +73,9 @@ Actions Advanced Features -* Conditions:: -* Consideration:: -* Setting the properties:: +* Conditions:: More than just DONE headings +* Consideration:: Only some of them +* Setting the properties:: The easy way to set BLOCKER and TRIGGER Conditions @@ -705,22 +705,68 @@ following, PLANNING is either scheduled or deadline. Copy PLANNING info verbatim from the source heading to all targets. The argument to this form may be either a string or a symbol. - • PLANNING!(“[+|-|++|–]NTHING”) + • PLANNING!(“[+|-|++|–]NTHING[ [+|-]LANDING]”) Increment(+) or decrement(-) target’s PLANNING by N THINGs relative to either itself (+/-) or the current time (++/–). N is an integer - THING is one of y (years), m (months), d (days), h (hours), or M - (minutes) + THING is one of y (years), m (months), d (days), h (hours), M + (minutes), a (case-insensitive) day of the week or its + abbreviation, or the strings “weekday” or “wkdy”. + + If a day of the week is given as THING, move forward or backward N + weeks to find that day of the week. + + If one of “weekday” or “wkdy” is given as THING, move forward or + backward N days, moving forward or backward to the next weekday. + + This form may also include a “landing” specifier to control where + in the week the final date lands. LANDING may be one of the + following: + + • A day of the week, which means adjust the final date forward + (+) or backward (-) to land on that day of the week. + + • One of “weekday” or “wkdy”, which means adjust the target date + to the closest weekday. + + • One of “weekend” or “wknd”, which means adjust the target date + to the closest weekend. + + • PLANNING!(“float [+|-|++|–]N DAYNAME[ MONTH[ DAY]]”) + + Set time to the date of the Nth DAYNAME before/after MONTH DAY, as + per ‘diary-float’. + + N is an integer. + + DAYNAME may be either an integer, where 0=Sunday, 1=Monday, etc., + or a string for that day. + + MONTH may be an integer, 1-12, or a month’s string. If MONTH is + empty, the following (+) or previous (-) month relative to the + target’s time (+/-) or the current time (++/–). + + DAY is an integer, or empty or 0 to use the first of the month (+) + or the last of the month (-). Examples: - scheduled!(“Mon 09:00”) -> Set SCHEDULED to the following Monday at -9:00 deadline!(“++1h”) -> Set DEADLINE to one hour from now. -deadline!(copy) deadline!(“+1h”) -> Copy the source deadline to the -target, then increment it by an hour. + • scheduled!(“Mon 09:00”) -> Set SCHEDULED to the following Monday at + 9:00 + • deadline!(“++2h”) -> Set DEADLINE to two hours from now. + • deadline!(copy) deadline!(“+1h”) -> Copy the source deadline to the + target, then increment it by an hour. + • scheduled!(“+1wkdy”) -> Set SCHEDULED to the next weekday + • scheduled!(“+1d +wkdy”) -> Same as above + • deadline!(“+1m -wkdy”) -> Set SCHEDULED up one month, but move + backward to find a weekend + • scheduled!(“float 2 Tue Feb”) -> Set SCHEDULED to the second + Tuesday in the following February + • scheduled!(“float 3 Thu”) -> Set SCHEDULED to the third Thursday in + the following month File: org-edna.info, Node: TODO State, Next: Archive, Prev: Scheduled/Deadline, Up: Actions @@ -851,9 +897,9 @@ Advanced Features * Menu: -* Conditions:: -* Consideration:: -* Setting the properties:: +* Conditions:: More than just DONE headings +* Consideration:: Only some of them +* Setting the properties:: The easy way to set BLOCKER and TRIGGER File: org-edna.info, Node: Conditions, Next: Consideration, Up: Advanced Features @@ -975,7 +1021,7 @@ File: org-edna.info, Node: Consideration, Next: Setting the properties, Prev: Consideration ============= -Special keyword that’s only valid for blockers. +“Consideration” is a special keyword that’s only valid for blockers. This keyword can allow specifying only a portion of tasks to consider: @@ -1206,62 +1252,62 @@ We can then merge that into the main development branch. Tag Table: Node: Top225 -Node: Copying3142 -Node: Introduction3959 -Node: Installation and Setup4907 -Node: Basic Operation5700 -Node: Blockers7551 -Node: Triggers7837 -Node: Syntax8099 -Node: Basic Features8789 -Node: Finders9092 -Node: ancestors10595 -Node: chain-find11179 -Node: children12517 -Node: descendants12916 -Node: file13426 -Node: first-child14175 -Node: ids14423 -Node: match15084 -Node: next-sibling15722 -Node: next-sibling-wrap15967 -Node: olp16269 -Node: org-file16681 -Node: parent17326 -Node: previous-sibling17512 -Node: rest-of-siblings17756 -Node: self18019 -Node: siblings18175 -Node: siblings-wrap18433 -Node: Actions18666 -Node: Scheduled/Deadline19408 -Node: TODO State20998 -Node: Archive21366 -Node: Chain Property21686 -Node: Clocking21969 -Node: Property22381 -Node: Priority22703 -Node: Tag23272 -Node: Effort23489 -Node: Advanced Features23878 -Node: Conditions24090 -Node: done24705 -Node: headings24869 -Node: todo-state25245 -Node: variable-set25501 -Node: has-property25930 -Node: re-search26199 -Node: Negating Conditions26559 -Node: Consideration26946 -Node: Setting the properties28153 -Node: Extending Edna29233 -Node: Naming Conventions29723 -Node: Finders (1)30186 -Node: Actions (1)30552 -Node: Conditions (1)31017 -Node: Contributing31907 -Node: Bugs32379 -Node: Development32731 +Node: Copying3268 +Node: Introduction4085 +Node: Installation and Setup5033 +Node: Basic Operation5826 +Node: Blockers7677 +Node: Triggers7963 +Node: Syntax8225 +Node: Basic Features8915 +Node: Finders9218 +Node: ancestors10721 +Node: chain-find11305 +Node: children12643 +Node: descendants13042 +Node: file13552 +Node: first-child14301 +Node: ids14549 +Node: match15210 +Node: next-sibling15848 +Node: next-sibling-wrap16093 +Node: olp16395 +Node: org-file16807 +Node: parent17452 +Node: previous-sibling17638 +Node: rest-of-siblings17882 +Node: self18145 +Node: siblings18301 +Node: siblings-wrap18559 +Node: Actions18792 +Node: Scheduled/Deadline19534 +Node: TODO State23109 +Node: Archive23477 +Node: Chain Property23797 +Node: Clocking24080 +Node: Property24492 +Node: Priority24814 +Node: Tag25383 +Node: Effort25600 +Node: Advanced Features25989 +Node: Conditions26327 +Node: done26942 +Node: headings27106 +Node: todo-state27482 +Node: variable-set27738 +Node: has-property28167 +Node: re-search28436 +Node: Negating Conditions28796 +Node: Consideration29183 +Node: Setting the properties30415 +Node: Extending Edna31495 +Node: Naming Conventions31985 +Node: Finders (1)32448 +Node: Actions (1)32814 +Node: Conditions (1)33279 +Node: Contributing34169 +Node: Bugs34641 +Node: Development34993 End Tag Table diff --git a/org-edna.org b/org-edna.org index cd34193..804ee5e 100644 --- a/org-edna.org +++ b/org-edna.org @@ -571,20 +571,62 @@ PLANNING is either scheduled or deadline. Copy PLANNING info verbatim from the source heading to all targets. The argument to this form may be either a string or a symbol. -- PLANNING!("[+|-|++|--]NTHING") +- PLANNING!("[+|-|++|--]NTHING[ [+|-]LANDING]") Increment(+) or decrement(-) target's PLANNING by N THINGs relative to either itself (+/-) or the current time (++/--). N is an integer - THING is one of y (years), m (months), d (days), h (hours), or M (minutes) + THING is one of y (years), m (months), d (days), h (hours), M (minutes), a + (case-insensitive) day of the week or its abbreviation, or the strings + "weekday" or "wkdy". + + If a day of the week is given as THING, move forward or backward N weeks to + find that day of the week. + + If one of "weekday" or "wkdy" is given as THING, move forward or backward N + days, moving forward or backward to the next weekday. + + This form may also include a "landing" specifier to control where in the week + the final date lands. LANDING may be one of the following: + + - A day of the week, which means adjust the final date forward (+) or backward + (-) to land on that day of the week. + + - One of "weekday" or "wkdy", which means adjust the target date to the + closest weekday. + + - One of "weekend" or "wknd", which means adjust the target date to the + closest weekend. + +- PLANNING!("float [+|-|++|--]N DAYNAME[ MONTH[ DAY]]") + + Set time to the date of the Nth DAYNAME before/after MONTH DAY, as per + ~diary-float~. + + N is an integer. + + DAYNAME may be either an integer, where 0=Sunday, 1=Monday, etc., or a string + for that day. + + MONTH may be an integer, 1-12, or a month's string. If MONTH is empty, the + following (+) or previous (-) month relative to the target's time (+/-) or the + current time (++/--). + + DAY is an integer, or empty or 0 to use the first of the month (+) or the last + of the month (-). Examples: -scheduled!("Mon 09:00") -> Set SCHEDULED to the following Monday at 9:00 -deadline!("++1h") -> Set DEADLINE to one hour from now. -deadline!(copy) deadline!("+1h") -> Copy the source deadline to the target, then increment it by an hour. +- scheduled!("Mon 09:00") -> Set SCHEDULED to the following Monday at 9:00 +- deadline!("++2h") -> Set DEADLINE to two hours from now. +- deadline!(copy) deadline!("+1h") -> Copy the source deadline to the target, then increment it by an hour. +- scheduled!("+1wkdy") -> Set SCHEDULED to the next weekday +- scheduled!("+1d +wkdy") -> Same as above +- deadline!("+1m -wkdy") -> Set SCHEDULED up one month, but move backward to find a weekend +- scheduled!("float 2 Tue Feb") -> Set SCHEDULED to the second Tuesday in the following February +- scheduled!("float 3 Thu") -> Set SCHEDULED to the third Thursday in the following month *** TODO State :PROPERTIES: :CUSTOM_ID: todo!