Function: org-clone-subtree-with-time-shift

org-clone-subtree-with-time-shift is an interactive and byte-compiled function defined in org.el.gz.

Signature

(org-clone-subtree-with-time-shift N &optional SHIFT)

Documentation

Clone the task (subtree) at point N times.

The clones will be inserted as siblings.

In interactive use, the user will be prompted for the number of clones to be produced. If the entry has a timestamp, the user will also be prompted for a time shift, which may be a repeater as used in time stamps, for example +3d. To disable this, you can call the function with a universal prefix argument.

When a valid repeater is given and the entry contains any time stamps, the clones will become a sequence in time, with time stamps in the subtree shifted for each clone produced. If SHIFT is nil or the empty string, time stamps will be left alone. The ID property of the original subtree is removed.

In each clone, all the CLOCK entries will be removed. This prevents Org from considering that the clocked times overlap.

If the original subtree did contain time stamps with a repeater, the following will happen:
- the repeater will be removed in each clone
- an additional clone will be produced, with the current, unshifted
  date(s) in the entry.
- the original entry will be placed *after* all the clones, with
  repeater intact.
- the start days in the repeater in the original entry will be shifted
  to past the last clone.
In this way you can spell out a number of instances of a repeating task, and still retain the repeater to cover future instances of the task.

As described above, N+1 clones are produced when the original subtree has a repeater. Setting N to 0, then, can be used to remove the repeater from a subtree and create a shifted clone with the original repeater.

Key Bindings

Source Code

;; Defined in /usr/src/emacs/lisp/org/org.el.gz
(defun org-clone-subtree-with-time-shift (n &optional shift)
  "Clone the task (subtree) at point N times.
The clones will be inserted as siblings.

In interactive use, the user will be prompted for the number of
clones to be produced.  If the entry has a timestamp, the user
will also be prompted for a time shift, which may be a repeater
as used in time stamps, for example `+3d'.  To disable this,
you can call the function with a universal prefix argument.

When a valid repeater is given and the entry contains any time
stamps, the clones will become a sequence in time, with time
stamps in the subtree shifted for each clone produced.  If SHIFT
is nil or the empty string, time stamps will be left alone.  The
ID property of the original subtree is removed.

In each clone, all the CLOCK entries will be removed.  This
prevents Org from considering that the clocked times overlap.

If the original subtree did contain time stamps with a repeater,
the following will happen:
- the repeater will be removed in each clone
- an additional clone will be produced, with the current, unshifted
  date(s) in the entry.
- the original entry will be placed *after* all the clones, with
  repeater intact.
- the start days in the repeater in the original entry will be shifted
  to past the last clone.
In this way you can spell out a number of instances of a repeating task,
and still retain the repeater to cover future instances of the task.

As described above, N+1 clones are produced when the original
subtree has a repeater.  Setting N to 0, then, can be used to
remove the repeater from a subtree and create a shifted clone
with the original repeater."
  (interactive "nNumber of clones to produce: ")
  (unless (wholenump n) (user-error "Invalid number of replications %s" n))
  (when (org-before-first-heading-p) (user-error "No subtree to clone"))
  (let* ((beg (save-excursion (org-back-to-heading t) (point)))
	 (end-of-tree (save-excursion (org-end-of-subtree t t) (point)))
	 (shift
	  (or shift
	      (if (and (not (equal current-prefix-arg '(4)))
		       (save-excursion
			 (goto-char beg)
			 (re-search-forward org-ts-regexp-both end-of-tree t)))
		  (read-from-minibuffer
		   "Date shift per clone (e.g. +1w, empty to copy unchanged): ")
		"")))			;No time shift
	 (doshift
	  (and (org-string-nw-p shift)
	       (or (string-match "\\`[ \t]*\\([+-]?[0-9]+\\)\\([hdwmy]\\)[ \t]*\\'"
				 shift)
		   (user-error "Invalid shift specification %s" shift)))))
    (goto-char end-of-tree)
    (unless (bolp) (insert "\n"))
    (let* ((end (point))
	   (template (buffer-substring beg end))
	   (shift-n (and doshift (string-to-number (match-string 1 shift))))
	   (shift-what (pcase (and doshift (match-string 2 shift))
			 (`nil nil)
			 ("h" 'hour)
			 ("d" 'day)
			 ("w" (setq shift-n (* 7 shift-n)) 'day)
			 ("m" 'month)
			 ("y" 'year)
			 (_ (error "Unsupported time unit"))))
	   (nmin 1)
	   (nmax n)
	   (n-no-remove -1)
	   (org-id-overriding-file-name (buffer-file-name (buffer-base-buffer)))
	   (idprop (org-entry-get beg "ID")))
      (when (and doshift
		 (string-match-p "<[^<>\n]+ [.+]?\\+[0-9]+[hdwmy][^<>\n]*>"
				 template))
	(delete-region beg end)
	(setq end beg)
	(setq nmin 0)
	(setq nmax (1+ nmax))
	(setq n-no-remove nmax))
      (goto-char end)
      (cl-loop for n from nmin to nmax do
	       (insert
		;; Prepare clone.
		(with-temp-buffer
		  (insert template)
		  (org-mode)
		  (goto-char (point-min))
		  (org-fold-show-subtree)
		  (and idprop (if org-clone-delete-id
				  (org-entry-delete nil "ID")
				(org-id-get-create t)))
		  (unless (= n 0)
		    (while (re-search-forward org-clock-line-re nil t)
		      (delete-region (line-beginning-position)
				     (line-beginning-position 2)))
		    (goto-char (point-min))
		    (while (re-search-forward org-drawer-regexp nil t)
		      (org-remove-empty-drawer-at (point))))
		  (goto-char (point-min))
		  (when doshift
		    (while (re-search-forward org-ts-regexp-both nil t)
		      (org-timestamp-change (* n shift-n) shift-what))
		    (unless (= n n-no-remove)
		      (goto-char (point-min))
		      (while (re-search-forward org-ts-regexp nil t)
			(save-excursion
			  (goto-char (match-beginning 0))
			  (when (looking-at "<[^<>\n]+\\( +[.+]?\\+[0-9]+[hdwmy]\\)")
			    (delete-region (match-beginning 1) (match-end 1)))))))
		  (buffer-string)))))
    (goto-char beg)))