I’m using the mu4e email client. One of the best things about having my mail moved to Emacs is the ease of tweaking my setup. Since I always have troubles with mental translation between a calendar date and expressions like “today”, “tomorrow”, “yesterday” and so on, I decided that Emacs might help me with that.
The default way mu4e displays the date is the so-called “human date”: time of day for today’s mail and date otherwise. This is not really that great, though: for once, it is not extremely helpful when I check my email just after midnight (which happens a lot), and I don’t have any visual clues about which non-today’s email is “recent”, neither. So I came with this simple hack (note: see below for an update!):
(defsubst mu4e~headers-human-date (msg) "Show a 'human' date. If the date is today or yesterday, show the time, otherwise, show the date. The formats used for date and time are `mu4e-headers-date-format' and `mu4e-headers-time-format'." (let ((date (mu4e-msg-field msg :date))) (if (equal date '(0 0 0)) "None" (let ((day1 (decode-time date)) (day2 (decode-time (current-time)))) (cond ((and (eq (nth 3 day1) (nth 3 day2)) ;; day (eq (nth 4 day1) (nth 4 day2)) ;; month (eq (nth 5 day1) (nth 5 day2))) ;; year (format-time-string mu4e-headers-time-format date)) ((eq (- (time-to-days (current-time)) (time-to-days date)) 1) (format-time-string mu4e-headers-yesterday-time-format date)) (t (format-time-string mu4e-headers-date-format date))))))) (defcustom mu4e-headers-yesterday-time-format "Y-%X" "Time format to use in the headers view for yesterday's messages. In the format of `format-time-string'." :type 'string :group 'mu4e-headers)
Now the “human date” displays yesterday’s emails with a timestamp preceded by Y-
. (Notice that this is defsubst
, so you need to re-evaluate the defun
s of all functions calling mu4e~headers-human-date
. Happily, there’s only one of them.
I’ve been using this now for a few days and I have to say I like it very much. (The only drawback is that it slows down displaying of large email lists, but I usually display at most two days’ worth of mail, and the slowdown is not really noticeable anyway in my experience.)
What I especially like about this is how much time I needed to pull this trick off. Starting with the idea, I needed just 16 minutes (yes, I use Org-mode clocking)! This was first grepping the manual for the :human-date
field; then grepping the sources for names of the function which use it; then checking the function responsible for this format; then checking in the Emacs Lisp Reference for functions operating on time data; then thinking for a moment about the implementation (I decided that subtracting the results of time-to-days
is easier than manually checking for last/first days of months and years, although this is definitely not the fastest way from the computer point of view); then actually implementing it (abo-abo’s lispy is great, for instance, it allows for instant conversion between cond
and (potentially nested) if
s), and rudimentary testing. This is exactly the power of easy customization thanks to source-openness and self-documentingness of Emacs.
Take that, Outlook. Or even Thunderbird. Or even Mutt.
Update: after sharing the above snippet on the mu-discuss
mailing list, Dirk-Jan C. Binnema (the author of mu and mu4e) corrected a small and stupid bug (this is already taken care in the code above) and suggested using mu4e’s custom headers. After a short exchange (I had a problem setting it up due to a quoting mistake) the following code emerged:
(defun mu4e~headers-more-human-date (msg) "Show a 'more human' date. If the date is today or yesterday, show the time, otherwise, show the date. The formats used for date and time are `mu4e-headers-date-format' and `mu4e-headers-time-format'." (let ((date (mu4e-msg-field msg :date))) (if (equal date '(0 0 0)) "None" (let ((day1 (decode-time date)) (day2 (decode-time (current-time)))) (cond ((and (eq (nth 3 day1) (nth 3 day2)) ;; day (eq (nth 4 day1) (nth 4 day2)) ;; month (eq (nth 5 day1) (nth 5 day2))) ;; year (format-time-string mu4e-headers-time-format date)) ((eq (- (time-to-days (current-time)) (time-to-days date)) 1) (format-time-string mu4e-headers-yesterday-time-format date)) (t (format-time-string mu4e-headers-date-format date))))))) (defcustom mu4e-headers-yesterday-time-format "Y-%X" "Time format to use in the headers view for yesterday's messages. In the format of `format-time-string'." :type 'string :group 'mu4e-headers) (add-to-list 'mu4e-header-info-custom '(:more-human-date . (:name "Date" :shortname "Date" :help "Date in even more human-friendly format" :function mu4e~headers-more-human-date))) (setq mu4e-headers-fields '((:more-human-date . 12) (:flags . 6) (:mailing-list . 10) (:from . 22) (:subject)))
As you can see, here we avoid modifying mu4e’s internal function – instead, we copy it and modify the copy – and we don’t lose the possibility of using the original :human-date
.
Also, since writing the above paragraph about the speed of my code, I did in fact look into the code of time-to-days
. It turns out that my implementation indeed is a very inefficient one – but since it seems to work fast enough, I decided not to change it.