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 defuns 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) ifs), 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.