1

It seems to me I can either set a time for today or some relative time in the future.

But can I combine the two somehow to set a timer for 7am tomorrow, for example?

4

Yes - call run-at-time using the encode-time method to pass the time argument as mentioned in its doc string:

(run-at-time TIME REPEAT FUNCTION &rest ARGS)

  Probably introduced at or before Emacs version 19.26.

Perform an action at time TIME.
Repeat the action every REPEAT seconds, if REPEAT is non-nil.
REPEAT may be an integer or floating point number.
TIME should be one of:
- a string giving today's time like "11:23pm"
  (the acceptable formats are HHMM, H:MM, HH:MM, HHam, HHAM,
  HHpm, HHPM, HH:MMam, HH:MMAM, HH:MMpm, or HH:MMPM;
  a period `.' can be used instead of a colon `:' to separate
  the hour and minute parts);
- a string giving a relative time like "90" or "2 hours 35 minutes"
  (the acceptable forms are a number of seconds without units
  or some combination of values using units in `timer-duration-words');
- nil, meaning now;
- a number of seconds from now;
- a value from `encode-time';
- or t (with non-nil REPEAT) meaning the next integral
  multiple of REPEAT.

The action is to call FUNCTION with arguments ARGS.

This function returns a timer object which you can use in
`cancel-timer'.

So you can say

(run-at-time (encode-time '(0 0 7 13 6 2020 nil t nil)) nil #+my-func)

to run my-func once at 7am EDT (local time) on 2020-06-13.

N.B. For versions of emacs < 27 (?), encode-time had a different signature, so the above needs to be modified like this:

(run-at-time (apply #'encode-time '(0 0 7 13 6 2020 nil t nil)) nil #+my-func)

Calculating tomorrow from today is not difficult (there may be better ways to do that than the one I use below which was thrown together in a hurry):

(defun tomorrow ()
  ;;; the `(nthcdr 3 ...)' gets rid of the SECOND, MINUTE, HOUR values
  (let ((now-plus-1-day (nthcdr 3 (decode-time (+ (* 24 60 60) 
                                                  (time-to-seconds (current-time)))))))
    ;;; now keep three entries and get rid of the DST,TIMEZONE entries
    (setf (nthcdr 3 now-plus-1-day) nil)
    ;;; return (MONTH DAY YEAR)
    now-plus-1-day))

This returns a three-element list (MONTH DAY YEAR).

So now you can combine the two and say

(run-at-time (encode-time `(0 0 7 ,@(tomorrow) nil t nil)) nil #+my-func)

using the backquote mechanism to splice in the list that tomorrow returns.

N.B. For versions of emacs < 27 (?), encode-time had a different signature, so the above needs to be modified like this:

(run-at-time (apply #'encode-time '(0 0 7 ,@(tomorrow) nil t nil)) nil #+my-func)

You should check the doc string of encode-time as well (the following is from bleeding-edge emacs 28.0.50 - as noted above, for emacs versions < 27(?), the signature of encode-time was different, so check the doc string locally):

encode-time is a built-in function in `C source code'.

(encode-time TIME &rest OBSOLESCENT-ARGUMENTS)

  Probably introduced at or before Emacs version 19.29.
  This function does not change global state, including the match data.

Convert TIME to a timestamp.

TIME is a list (SECOND MINUTE HOUR DAY MONTH YEAR IGNORED DST ZONE).
in the style of `decode-time', so that (encode-time (decode-time ...)) works.
In this list, ZONE can be nil for Emacs local time, t for Universal
Time, `wall' for system wall clock time, or a string as in the TZ
environment variable.  It can also be a list (as from
`current-time-zone') or an integer (as from `decode-time') applied
without consideration for daylight saving time.  If ZONE specifies a
time zone with daylight-saving transitions, DST is t for daylight
saving time, nil for standard time, and -1 to cause the daylight
saving flag to be guessed.

As an obsolescent calling convention, if this function is called with
6 or more arguments, the first 6 arguments are SECOND, MINUTE, HOUR,
DAY, MONTH, and YEAR, and specify the components of a decoded time,
where DST assumed to be -1 and FORM is omitted.  If there are more
than 6 arguments the *last* argument is used as ZONE and any other
extra arguments are ignored, so that (apply #'encode-time
(decode-time ...)) works.  In this obsolescent convention, DST and
ZONE default to -1 and nil respectively.

Years before 1970 are not guaranteed to work.  On some systems,
year values as low as 1901 do work.

EDIT: added some backward-compatibiity notes for encode-time. I'm not sure when the signature changed but a comment indicates that 26.2 uses the old implementation and my experiments with 26.3 indicate the same thing. I have not tested emacs-27, hence the question marks.

Thanks to @Tom for pointing the problem out.

| improve this answer | |
  • Thanks for the detailed answer. I missed encode-time, because I was reading the info manual, and it's only a single sentence there among lots of other info. BTW, it would be more convenient if the user could simply specify "7am 1 day" meaning 7am a day from now. Strange that it's not supported, because the elements are there. – Tom Jun 12 at 17:32
  • That's a UI matter and e.g. org-read-date allows some things like that but not exactly what you want: maybe (probably?) someone has written a function to translate human-friendly specs like "7am tomorrow" to the format that run-at-time expects. If not, maybe you can write your own implementation: if it's good enough, then everybody will adopt it! The main problem will be to detect ambiguity and inform the (human) user so that they can fix it. – NickD Jun 12 at 18:00
  • I use orgmode for scheduling and its date read function is really convenient, so it could probably be adapted for use in run-at-time in some form. Orgmode is part of emacs, so it would make sense to reuse the code, instead of writing it from scratch. – Tom Jun 12 at 18:04
  • But AFAIK it cannot combine times and relative dates: 7am +1d is not understood. There may be other limitations. – NickD Jun 12 at 18:11
  • If it's past 7am then 7am in orgmode gives you 7am tomorrow which is nice. run-at-time could do this by default. And if it's before 7am and you want 7am tomorrow then I usually use the day. So if it's 6am Sat then 7am Sun sets 7am to tomorrow. – Tom Jun 12 at 18:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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