Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. It's 100% free, no registration required.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I have following value set for index_format in mutt:

"%Z %{%Y %b %e  %H:%M} %?X?(%X)&   ? %-22.22F  %.100s %> %5c "

which displays date in the format as

2013 Dec 5

I was wondering whether it is possible to have different date formats depending on how old the email is. By that I mean:

for less than 7 days:  today, yesterday, tuesday, monday
this year:             Dec 5
older than this year:  2013 Dec 5

I think I have seen this functionality in Thunderbird. Would be nice to have it in mutt

share|improve this question
up vote 3 down vote accepted

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.

share|improve this answer
If you are using version 1.5+ (which you absolutely should), there is a way. Suggesting rewriting the date headers is hilarious, though… – hop Jan 28 '14 at 19:33
@hop FWIW, your answer got my upvote. – Michael Kjörling May 13 '14 at 20:44

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 you PATH.

Two things are important here:

  • The format string has to contain on 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]" |'
share|improve this answer
I have not had the time to debug this yet, but there seems to be a problem with this solution and subjects that contain a % sign. Patches would be appreciated! – hop Oct 9 '14 at 23:41

Made some modifications, but didn't solve the "% in subject" issue

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

#define DAY (time_t)86400
#define WEEK (time_t)604800
#define MONTH (time_t)2678400
#define YEAR (time_t)31556926

/*I use this line in .muttrc: 
 * set index_format        = '/home/marcus/.mutt/mfdate "%9[%d.%m.%y]" "%9[%e.%b]" " [%6[%e.%b]]" "%8[%a %H:%m]" "    %[%H:%m]" "%Z %%s %?X?%2X&  ? %-20.20L %?M?+%-2M&   ? %.86s %> [%4c]asladfg" "%[%s]" |'*/
int main(int argc, const char *argv[]) {
    time_t current_time;
    time_t message_time;
    struct tm *ltime;
    unsigned int todays_seconds=0;
    unsigned int seconds_this_morning=0;

    const char *last_year, *this_year, *last_months, *last_week, *today;
    const char *format;

    current_time = time(NULL);
    ltime = localtime(&current_time);
    todays_seconds = ltime->tm_hour*3600 + ltime->tm_min*60 + ltime->tm_sec;
    seconds_this_morning = current_time - todays_seconds;

    if (argc!=8) {
        printf("Usage: %s last_year this_year today format timestamp\n", argv[0]);
        return 2;

    last_year    = argv[1];
    this_year    = argv[2];
    last_months  = argv[3];
    last_week    = argv[4];
    today        = argv[5];

    format       = argv[6];

    message_time = atoi(argv[7]);

     *if ((message_time+YEAR) < current_time) {
     *    printf(format, last_year);
     *} else if ((message_time+MONTH) < current_time) {
     *    printf(format, this_year);
     *} else if ((message_time+WEEK) < current_time) {
     *    printf(format, last_months);
     *} else if ((message_time+DAY) < current_time) {
     *    printf(format, last_week);
     *} else {
     *    printf(format, today);

    if ((message_time/YEAR) < (current_time/YEAR)) {
        printf(format, last_year);
    } else if ((message_time/MONTH) < (current_time/MONTH)) {
        printf(format, this_year);
    } else if ((message_time + WEEK) < current_time) {
    /*} else if ((message_time/DAY) < (current_time/DAY)) {*/
        printf(format, last_months);
     *} else if ((message_time+DAY) < current_time) {
     *    printf(format, last_week);
    } else if ((message_time ) < seconds_this_morning) {
        printf(format, last_week);
    } else {
        printf(format, today);

    return 0;
share|improve this answer
It would be good if you summarised the changes you've made and the reasons behind them. – zagrimsan May 2 at 9:15

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

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