emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] master d0f7e21 085/135: Added two new forms for setting planning


From: Ian Dunn
Subject: [elpa] master d0f7e21 085/135: Added two new forms for setting planning information
Date: Mon, 17 Feb 2020 10:52:57 -0500 (EST)

branch: master
commit d0f7e21f9a20407d2032e1871bcc68be850c12f4
Author: Ian Dunn <address@hidden>
Commit: Ian Dunn <address@hidden>

    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!



reply via email to

[Prev in Thread] Current Thread [Next in Thread]