15

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

6 Answers 6

16

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]" |'
2
  • 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!
    – user601
    Commented Oct 9, 2014 at 23:41
  • 1
    I have created a bounty. Do you have any ideas how to fix the bug ? Commented Apr 30, 2017 at 10:56
6
+50

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.

2
  • 2
    If you are using version 1.5+ (which you absolutely should), there is a way. Suggesting rewriting the date headers is hilarious, though…
    – user601
    Commented Jan 28, 2014 at 19:33
  • @hop FWIW, your answer got my upvote.
    – user
    Commented May 13, 2014 at 20:44
4

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.

4

Edited @Marcus' c version a bit (still no solution to the % in the subject issue though):

// -*- coding:utf-8-unix; mode:c; -*-
/*
    Sets mutt index date based on mail age.

build:
    gcc mutt-index-date-formatter.c -o mutt-index-format
use this line in .muttrc:
    set index_format = 'mutt-index-format "%9[%d.%m.%y]" "%9[%e.%b]" "%8[%a %H:%m]" "%[%H:%m]" "%3C [%Z] %?X?%2X& -? %%s %-20.20L %?M?+%-2M&   ? %s %> [%4c]asladfg" "%[%s]" |'*/
// ////////////////////////////////////////////////////////////////

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

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

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;
    char *concat_str;

    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;  // unix time @ 00:00

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

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

    format       = argv[5];

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

    if (message_time >= seconds_this_morning) {
        asprintf(&concat_str, "    %s", today);
        printf(format, concat_str);
    } else if (message_time >= seconds_this_morning - DAY) {
        asprintf(&concat_str, "ydy %s", today);
        printf(format, concat_str);
    } else if (message_time > seconds_this_morning - WEEK) {
        printf(format, last_week);
    } else if (message_time/YEAR < current_time/YEAR) {
        printf(format, last_year);
    } else {
        printf(format, this_year);
    }

    return 0;
}

This formats dates as following (all times are in 24h format):

  • 02:04 for today's mail
  • ydy 02:04 for yesterday's mail
  • Thu 02:04 for last 7 days mail
  • 27.Mar for current year's mail
  • 13.12.16 for previous years' mail

Full index format in this example is #no [flags] #no_of_attachments date sender subject msg_size

3

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;
}
1
  • It would be good if you summarised the changes you've made and the reasons behind them.
    – zagrimsan
    Commented May 2, 2016 at 9:15
2

This index_format variable

set index_format='mfdate "%[%s]" "%4C %Z %[!%b %d %Y] %-17.17F (%3l) %s" |'

together with this modified mfdate.c presented in this answer by user hop:

#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 = "old";
  char *recent = "recent";
  char *today = "today";
  const char *format;

  current_time = time(NULL);

  if (argc != 3) {
    printf("Usage: %s format\n", argv[0]);
    return EXIT_FAILURE;
  }

  format = argv[2];

  message_time = atoi(argv[1]);

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

  return EXIT_SUCCESS;
}

works correctly for me in mutt 1.6.1 and as you see there are no problems with % sign in the subject, if this is what the real problem was about:enter image description here

This is the initial "just working" version because after taking a closer look at your original question I am not sure if this is what you want. However, if this is what you want let me know and we'll think how to make it better.

EDIT:

It can also work with your preferred index_format:

set index_format='mfdate "%[%s]" "%%Z %%{%%Y %%b %%e  %%H:%%M} %%?X?(%%X)&   ? %%-22.22F  %%.100s %%> %%5c" |'

mfdate.c:

#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 = "old";
  char *recent = "recent";
  char *today = "today";
  const char *format;

  current_time = time(NULL);

  if (argc != 3) {
    printf("Usage: %s format\n", argv[0]);
    return EXIT_FAILURE;
  }

  format = argv[2];

  message_time = atoi(argv[1]);

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

  return 0;
}

enter image description here

EDIT:

Let me explain how it works:

The mfdate takes 2 arguments:

"%[%s]"

and:

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

The first argument is only time of the message, as described in index_format documentation in .muttrc:

# %[fmt]  the date and time of the message is converted to the local
#         time zone, and ``fmt'' is expanded by the library function
#         ``strftime''; a leading bang disables locales

In this case fmt is replaced with %s, because as %s means The number of seconds since the Epoch as explained in man strftime. The first argument is used to compute how old the message is and what label: old, recent or today it should have.

The second argument is the remaining part of the index_format variable. It's used in mfdate only for printing but an extra % is added at the end of printf because as it says in mutt manual:

The string returned will be used for display. If the returned string ends in %, it will be passed through the formatter a second time.

Every % is doubled here because we want to pass a literal % to the second formatting done by mutt.

1
  • Why the downvote? Anything wrong with this answer? Commented May 5, 2017 at 19:42

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .