💾 Archived View for librehacker.com › gemlog › starlog › 20240723-0.gmi captured on 2024-08-25 at 00:54:17. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2024-08-18)

-=-=-=-=-=-=-

Emacs: Timers for Weekly Events (publ. 2024-07-23)

A great feature of Emacs is the timer library. This includes `run-at-time' for starting a timer at a particular time (relative or absolute) and it can receive an argument for repeating the timer every 𝑥 number of seconds after that. You can list all your active timers with the `list-timers' command, which interface also includes a handy shortcut key for cancelling a timer.

It is not obvious, however, how to use run-at-time to set up a weekly timer event. It is possible to pass a time of day to run-at-time, but that forces the user to check what day of the week it is; also there is a quirk in the design of run-at-time, such that if you specify a time of day, the timer will immediately run if the time of day is already past that point. Now, in Elisp it is not too hard to calculate the number of seconds until some future date. But that is not very helpful since, if your run-at-time is part of your init.el, you would need to first calculate what that next future date would be, which is not trival.

To deal with this problem, I came up with my own function that calculates the number of seconds until a specified day, hour, and minute of the week:

(defun secs-until-weekly (tday thour tmin &optional current-time)
  "Calculate the number of seconds until the specified weekday, hour, and minute (TDAY, THOUR, TMIN). The values need to be in the range 0-6, 0-23, and 0-59, respectively. The target minute is understood to be the start of the minute, i.e., second 0 of that minute. Be aware that no correction is made for leap seconds."
  (let* ((current-time
          (if current-time current-time (current-time)))
         (decoded-time (decode-time current-time)))
    (let ((cday (decoded-time-weekday decoded-time))
          (chour (decoded-time-hour decoded-time))
          (cmin (decoded-time-minute decoded-time))
          (csec (decoded-time-second decoded-time)))
      (let ((c-weekly-sec
             (+ csec
                (* cmin 60)
                (* chour 3600)
                (* cday 86400)))
            (t-weekly-sec
             (+ (* tmin 60)
                (* thour 3600)
                (* tday 86400))))
        (if (>= t-weekly-sec c-weekly-sec)
            (- t-weekly-sec c-weekly-sec)
          (- (+ t-weekly-sec 604800) c-weekly-sec))))))

I'm currently testing this function with a once a week call to a function which stamps the time and date in the *Messages* log.

(defun the-time-and-date-now-is ()
  "Passes the date and time to `message'"
  (message (format-time-string "%F %H:%M" (current-time))))

(run-at-time (secs-until-weekly 2 4 0) 604800 'the-time-and-date-now-is)

One caveat is that secs-until-weekly does not make any adjustments for leap seconds. So to prevent any sizeable drift (over the centuries!) you'll want to make the run-at-time call non-repeating, and instead have your timer function grab the current time again and start a new timer using secs-until-weekly.

Copyright

This work © 2024 by Christopher Howard is licensed under Attribution-ShareAlike 4.0 International.

CC BY-SA 4.0 Deed