0

I would like to capture the time when I finish an org-capture template with C-c C-c.

Is it possible? And how is it possible to use the given templates (i.e., the template %U) for this?

Best Jonas

4
  • Captures are supposed to be very short: a few seconds in most cases, certainly less than a minute. The idea is to capture an item as quickly as possible so that it does not interrupt the work that you are doing. Refiling it and perhaps adding details, prioritizing, scheduling, and so on, is done later, perhaps on your daily review. Does it really make any difference when you finished the capture? You can probably cook up something with the :after-finalize property in the template or by using the org-capture-after-finalize-hook, but IMO it's probably not worth it. Why do you want it?
    – NickD
    Commented Jun 14, 2023 at 14:02
  • 1
    I use org-capture also for meeting notes that get sorted into a datetree. In my workflow, I have a capture-buffer open as long as the meeting or the call goes. I would love to automatically store the information when it ended...
    – grszkthfr
    Commented Jun 15, 2023 at 5:44
  • I see - I agree that it's a reasonable (if a bit off-the-beaten-path) use case.
    – NickD
    Commented Jun 15, 2023 at 6:28
  • BTW, I should have said :before-finalize and org-capture-before-finalize-hook: those are still done in the capture buffer; the after versions are done in the buffer where org-capture was invoked, which is not what you want. I think that's correct but I have not checked carefully.
    – NickD
    Commented Jun 15, 2023 at 20:40

1 Answer 1

3

AFAIK, template expansion happens at the very beginning of the capture, so you cannot use %U or similar escapes, since you can only add the end time at the end of the capture. But there are several hooks and properties that take effect when the capture is finalized. The relevant ones here are org-capture-before-finalize-hook and the :before-finalize property of a template.

The problem is that the hook is available in virtually all versions of Org mode (certainly any version >= 9.0), but the property is only available in versions greater than 9.5.5. In particular, the version of Org mode in Emacs 28.2 is 9.5.5, so the property is not available there[1].

I explain how to do it in both cases, but since the property is easier to explain, I will start with that.

Using :before-finalize property

If available, the property :before-finalize in a capture template specifies a function (of no arguments) that is called at the end of org-capture-finalize just before the pre-capture window configuration and current buffer are restored (in contrast, :after-finalize specifies a function that is run after the pre-capture window configuration and current buffer are restored). So we can write the template as follows:

(setq my-template '("z" "My template" entry
                     (file+headline "/path/to/foo.org" "Tasks")
                     "* TODO %?\n  %U %a"
                     :prepend t
                     :before-finalize my/capture-end-function))

and add it to the list of capture templates[2]:

(with-eval-after-load 'org-capture
  (add-to-list 'org-capture-templates my-template))

We still have to write a function my/capture-end-function to do what you want. Getting the time stamp is easy - the question is where to put it. I think that the the best place is to add it as a property, say CAPTURE-END, to the newly created entry:

(defun my/capture-end-function ()
  (let ((ts (format-time-string "[%Y-%m-%d %a %H:%M]")))
    (org-entry-put nil "CAPTURE-END" ts)))

This uses the current time to construct an inactive time stamp[3] and then sets the property CAPTURE-END to this value. The nice thing about this method is that you don't have to worry about where in the note the timestamp will appear: org-entry-put takes care of creating the PROPERTIES drawer (if it does not exist) in the right place. And you can retrieve the property of the current entry later with (org-entry-get nil "CAPTURE-END") - see the Property API section of the manual for more details.

Using org-capture-before-finalize-hook

If your version of Org mode is 9.5.5 or earlier, you will not be able to use the property method above. In that case, you can add a function to org-capture-finalize-hook to do the same thing. The difference is that the property is a property of a particular template, so it will only run its function for that template and for no other, whereas the hook is a blunter instrument: it will run its functions when any template is invoked, not just the one you want. The solution is to modify the function that you add to the hook to check which template it was invoked from and only act for the particular template you want; it will be a no-op in other templates.

You still have to define your template and add it to org-capture-templates as above. You then proceed as follows:

;; for 9.5.5 and below we do it with a hook
(add-hook 'org-capture-before-finalize-hook #'my/capture-end-hook-function)

(defun my/capture-end-hook-function ()
  (when (string= (plist-get org-capture-current-plist :key) "z")
    (let ((ts (format-time-string "[%Y-%m-%d %a %H:%M]")))
      (org-entry-put nil "CAPTURE-END" ts))))

The function is almost the same as before, except we now check that we are called from the z template, otherwise we do nothing.

As you can see, you need to know a bit more about the internals of the capture code to implement it (e.g. org-capture-current-plist is not covered in the manual - you have to read the code to discover it), but it is not complicated. Nevertheless, if the :before-finalize property is available to you, I think you should go with that.


Footnotes

[1] Of course, you could upgrade Org mode to a later version, but this is not covered in this answer.

[2] with-eval-after-load makes sure that we do not attempt to modify the value of the variable org-capture-templates before org-capture has been loaded and defined the variable in the first place.

[3] Inactive time stamps don't contribute to the agenda, so even if the file is an agenda file, you are not going to get meaningless agenda entries.

2
  • There were a couple of naming problems in the code: I think I fixed them but let me know if you run into problems.
    – NickD
    Commented Jun 19, 2023 at 22:18
  • 1
    Works perfectly! Thank you!
    – grszkthfr
    Commented Jun 21, 2023 at 5:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.