2026-05-18 Marking today’s files in Dired

As anyone reading my blog knows, I’m a big fan of Dired. One of its killer features is the set of marking commands, which allow marking files based on their extensions, names (regex-based), contents (also regex-based). There is also a “universal marking command”, dired-mark-sexp, which allows the user to provide an Elisp expression serving as a predicate and marks all files satisfying that predicate. What’s even more, you can use several symbols in that predicate, like size or name (head to the docs to learn more).

What I found lacking is an easy (that is, not requiring me to type a convoluted expression each time) way to mark “recently modified” files. Of course, being an Elisp fan, I wanted to write one (especially that I’ve never written a Dired marking function, and learning how to do that seemed pretty cool).

As is often the case, the hardest part is designing the UI to feel Emacs-y. Many Dired marking commands unmark “their” files with a prefix argument. I want to preserve the unmarking feature, but I’d like to use the prefix argument for something else. I want my command to mark files modified within the last N days when issued with prefix argument N (which is 1 by default, of course). It seems natural to unmark files most recently modified within the last -N days when called with a negative argument. As a mathematician, I cannot not notice that this lefts the prefix argument of 0 ambiguous – I decided that I can just decide arbitrarily that this could mark files modified within the last 60 minutes (which seems a useful idea). Of course, I could go further and handle raw prefix arguments like a lone minus or various numbers of C-u presses, but let’s not make this too complicated.

There are a few minor points I’d like to mention. One is the question whether to mark “files modified within the last 24/N*24 hours” or “files modified since last midnight/last Nth midnight”. Of course, the former is easier to code, and the latter is potentially more useful for the user – if I’m working in the morning, I may be interested in stuff I worked on today, but less so yesterday in the afternoon. This brings forward the other issue: should I count “midnight” in UTC or local time? Again, the former is simpler, but the latter makes more sense. And of course, one has to be careful with time calculations. In my first attempt, I first zeroed the hour, minute and second slots of current-time and then subtracted the number of seconds corresponding to N days – but that created a problem when called shortly after a DST change. The correct solution was obviously first to subtract N days from current-time and then zero the H/M/S slots.

Last but not least, I’d like my command to be easily callable, so I want to bind it to some sensible key. Most marking commands are on the * prefix, and * t (as in “time” or “today”) was already taken, but * r (as in “recent”) is not bound by default. I decided to use bind-keys which I learned about recently. So, let’s get coding!

(defun dired-mark-recent (days)
  "Mark files last modified at most (abs(DAYS)-1) days ago.
This means files modified since midnight if DAYS=1.  Unmark if DAYS is
negative. If DAYS=0, mark files last modified within the last 60 minutes."
  (interactive "P" dired-mode)
  (let* ((n (prefix-numeric-value days))
         (absn (abs n))
         (msg (format "recent (last %s) file"
                      (if (zerop n)
                          "60 minutes"
                        (format "%s day%s"
                                absn
                                (if (= absn 1) "" "s")))))
         (dired-marker-char (if (minusp n) ?\s dired-marker-char))
         (cutoff (if (zerop n)
                     (time-add (current-time) -3600) ; now - 60 minutes
                   (let ((time (decode-time
                                (time-add (current-time)
                                          (* (1- absn)
                                             60 60 24 -1)))))
                     (setf (decoded-time-hour time) 0
                           (decoded-time-minute time) 0
                           (decoded-time-second time) 0)
                     (encode-time time)))))
    (dired-mark-if
     (and (time-less-p
           cutoff
           (file-attribute-modification-time
            (file-attributes (dired-get-filename t t))))
          (not (looking-at-p dired-re-dot)))
     msg)))

(bind-key "* r" #'dired-mark-recent dired-mode-map)

As you can see, there’s nothing mysterious in the code itself. The gist is the (dired-get-filename t t) invocation within the dired-mark-if macro. The first t means it will return the filename relative to the “current directory” (which is called default-directory in Emacs), and the second one means not to error out if the point is not on a line with a filename. It has a side effect of treating . and .. like every other file, which we most probably don’t want here, hence the and. (I learned about the undocumented variable dired-re-dot from looking at dired.el.)

The future will show if this is really as useful as it seems. In the meantime – see you next time!

CategoryEnglish, CategoryBlog, CategoryEmacs, CategoryDired