4

I want to capture a journal entry several times a day. Here was my initial solution:

(setq org-capture-templates '(("w" "Weekly" item 
                               (file+olp+datetree "/home/joe/org-files/goodtime.org")
                               "%?" :tree-type week)))

(mapc
 (lambda (slot)
   (run-at-time slot
                (* 60 60 24) ; repeat each day at the same time if Emacs is still running
                (lambda ()
                  (interactive)
                  (org-capture nil "w")
                  (insert (format-time-string "%-H:%M " nil t)))))
 '("09:00am" "12:00pm" "3:00pm" "6:00pm" "9:00pm"))

This seems to work as expected if I start Emacs before 9AM.

However, if I start Emacs later than that, the Org Capture runs immediately, once for each time of day that has been missed. I see now that this is explained in the answer here: Is it possible to execute a function or command at a specific time?

The problem is that I want to run the task exactly at the times mentioned, not potentially at some other time.

Thinking out loud, one solution would be to write in an explicit conditional that takes note of the time when Emacs starts, checks whether that was within the last 24 hours, and runs the capture event or not as required. This seems pretty heavy for a one-off solution, and I wonder if actually run-at-time should be patched?


Before trying to do this with run-at-time I tried to use emacsclient inside of a cron job, but that didn't work for me. The script runs on its own, but I wasn't able to get it to work from within cron, which apparently doesn't play nicely with interactive "desktop" jobs.

3
  • How about putting in a condition to check the current-time (which is an Emacs function) and define the criteria that if it falls between such and such a range, or a list of ranges, then proceed with the chron job, else exit and do nothing ...? The time can be broken down into hours, minutes, a vector of time elements, etc ...
    – lawlist
    Jul 1, 2020 at 3:47
  • Yes I said something about that as a route forward, it just seems strange that everyone who wants to schedule tasks would have to re-write this functionality. Jul 1, 2020 at 12:52
  • 1
    Instead of using the convenient, but limited, "today" time format, maybe you can use encode-time format in run-at-time. Construct a list of the times, but make them either today or tomorrow, depending on what time emacs is started. Maybe this question will help.
    – NickD
    Jul 1, 2020 at 15:39

1 Answer 1

1

Here's some code that converts simple HH:MM time specifications to encoded times for either today or tomorrow, depending on whether the given time is later or earlier than the current time. There are three main functions:

  • convert-hh:mm-to-decoded converts a simple time spec in HH:MM format (N.B. no AM/PM specification - use 24-hour time instead) into the complete decode-time format assuming today's date.
  • shift-to-tomorrow takes a decoded-time format spec and shifts the date part to tomorrow.
  • shift-to-tomorrow-maybe takes a decoded-time format spec and figures out whether the time is earlier than now (in which case it calls shift-to-tomorrow on it) or later than now (in which case it returns it unchanged.

The functions can be pipelined together through mapcar. Since run-at-time takes encoded times as arguments, the only thing that changes in your code is the slot calculation, so the literal list of times becomes instead:

(mapcar #'encode-time
   (mapcar #'shift-to-tomorrow-maybe
      (mapcar #'convert-hh:mm-to-decoded
         '("09:00" "12:00" "15:00" "18:00" "21:00"))))

Here's an Org mode document with the functions in a source block, so they can be executed easily and the output checked (note that the last form does two of the three `mapcar's above, for ease of checking):

* Code

#+begin_src emacs-lisp
  (defun convert-hh:mm-to-decoded (ts)
     (interactive "sHH:MM: ")
     (let* ((split (split-string ts ":"))
            (h (string-to-number (nth 0 split)))
            (m (string-to-number (nth 1 split))))
       (append (list 0 m h) (date-part (today)))))

  (defun date-part (td)
    (nthcdr 3 td))

  (defun today ()
    (decode-time (current-time)))

  (defun tomorrow ()
    (decode-time (time-add 86400 (current-time))))

  (defun shift-to-tomorrow (time-date)
    (let ((td time-date))
      (setf (nthcdr 3 td) nil)
      (append td (date-part (tomorrow)))))

  (defun shift-to-tomorrow-maybe (time-date)
    (let ((td (encode-time time-date)))
      (if (time-less-p td nil)
        (shift-to-tomorrow time-date)
       time-date)))

  (setq times (mapcar #'shift-to-tomorrow-maybe 
                 (mapcar #'convert-hh:mm-to-encoded '("09:00" "12:00" "15:00" "18:00" "21:00"))))

  ; (mapcar #'encode-time times)
#+end_src

#+RESULTS:
| 0 | 0 |  9 | 5 | 7 | 2020 | 0 | t | -14400 |
| 0 | 0 | 12 | 5 | 7 | 2020 | 0 | t | -14400 |
| 0 | 0 | 15 | 5 | 7 | 2020 | 0 | t | -14400 |
| 0 | 0 | 18 | 4 | 7 | 2020 | 6 | t | -14400 |
| 0 | 0 | 21 | 4 | 7 | 2020 | 6 | t | -14400 |

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.