mutt: conditional date format in "index_format"

If you are using the "development" version of mutt (v1.5+) - and you absolutely should - there is the possibility to use an external filter as described in the manual.

First you need a script that can output different things according to the age of a message. Here is an example in Python:

#!/usr/bin/env python
"""mutt format date

Prints different index_format strings for mutt according to a
messages age.

The single command line argument should be a unix timestamp
giving the message's date (%{}, etc. in Mutt).
"""

import sys
from datetime import datetime

INDEX_FORMAT = "%Z {} %?X?(%X)&   ? %-22.22F  %.100s %> %5c%"

def age_fmt(msg_date, now):
    # use iso date for messages of the previous year and before
    if msg_date.date().year < now.date().year:
        return '%[%Y-%m-%d]'

    # use "Month Day" for messages of this year
    if msg_date.date() < now.date():
        return '%10[%b %e]'

    # if a message appears to come from the future
    if msg_date > now:
        return '  b0rken'

    # use only the time for messages that arrived today
    return '%10[%H:%m]'

if __name__ == '__main__':
    msg_date = datetime.fromtimestamp(int(sys.argv[1]))
    now = datetime.now()
    print INDEX_FORMAT.format(age_fmt(msg_date, now))

Save this as mutt-fmt-date somewhere on your PATH.

Two things are important here:

  • The format string has to contain one occurance of {} which is replaced with the return value of age_fmt() by Python.
  • The format string has to end with a % so that Mutt will interpret it.

Then you can use it in your .muttrc as follows:

set index_format="mutt-fmt-date %[%s] |"

Mutt will then

  1. interpret %[%s] according to the rules for format strings.
  2. call mutt-fmt-date with the result of 1. as argument (because of the | at the end).
  3. interpret what it gets back from the script as format string again (because of the % at the end).

Caveat: the script will be executed for every message that is to be about be displayed. The resulting delay can be quite noticable when scrolling through a mailbox.

Here is a version in C that performs somewhat adequately:

#include <stdlib.h>
#include <stdio.h>
#include <time.h>

#define DAY (time_t)86400
#define YEAR (time_t)31556926

int main(int argc, const char *argv[]) {
    time_t current_time;
    time_t message_time;

    const char *old, *recent, *today;
    const char *format;

    current_time = time(NULL);

    if (argc!=6) {
        printf("Usage: %s old recent today format timestamp\n", argv[0]);
        return 2;
    }

    old = argv[1];
    recent = argv[2];
    today = argv[3];

    format = argv[4];

    message_time = atoi(argv[5]);

    if ((message_time/YEAR) < (current_time/YEAR)) {
        printf(format, old);
    } else if ((message_time/DAY) < (current_time/DAY)) {
        printf(format, recent);
    } else {
        printf(format, today);
    }

    return 0;
}

This goes together with the muttrc line:

set index_format='mfdate "%[%d.%m.%y]" "%8[%e. %b]" "%8[%H:%m]" "%Z %%s %-20.20L %?y?[%-5.5y]&       ? %?M?+& ?%s%%" "%[%s]" |'

Unfortunately, that does not appear to be possible with current versions of Mutt.

$index_format supports a specific set of format specifiers, drawing from various message metadata. It is described in the Mutt manual (or here is the "stable" version's documentation for the same), and as you can see from the table, there are only a few format specifiers that are conditional. Those are %M, %y and %Y; %M is the number of hidden messages if the thread is collapsed, and %y and %Y are X-Label headers if present.

The actual formatting of the message date and time is done by strftime(3), which does not support conditional formatting at all.

It might be possible to do an ugly workaround by continually rewriting the message files' Date: headers, but I wouldn't want to do that at least. However, it's the least bad possibility that I can think of.

The only real solution I can think of would be to either implement such support in Mutt (which almost certainly is how Thunderbird does it), or write a replacement strftime which supports conditional formatting and inject that using LD_PRELOAD or a similar mechanism. The latter, however, will affect all date and time display in Mutt that goes through strftime, not only relating to the message index.


For some reason newer versions of mutt (1.7 showed that problem) prefix the date string with characters '14' and '32', which keep atoi from converting the string to an int. Changing the line to

message_time = atoi(2+argv[7]);

Possibly a stupid solution, but it works for me.

Tags:

Date

Mutt