Fixing received dates on IMAP messages

I recently tidied up much of my mail. It is all stored in Maildir++ form and I used to use Cyrus IMAP to access it. Many months ago I moved to Dovecot, but only recently renamed the directories from '.INBOX.Example" to ".Example".

To test that Dovecot, my mail reader, prostfix, sieve, deliver, etc would all work correctly I backed up one directoy, renamed it and used it as that test. It all worked correctly, so I copied the backup over the original -- then I noticed that Apple Mail suddenly was convinced that all of the mail in that folder had arrived at the time that I copied the messages. A little poking around showed that Apple Mail uses the file modification time as the received time.

 

I threw together a small Python script to open each file, parse out the time and date and then update the modification time to be correct.

 

This script requires mx.DateTime from www.egenix.com/files/python/mxDateTime.html so that it can correctly parse the mail date.


 

#!/usr/bin/python2.4 # Copyright 2006-2007 Warren Kumari. All Rights Reserved. """Fixed the date on messages in an Maildir++ style mailbox.. If you copy a Maildir++ style mailbox (and aren't careful!) the dates on the messages all show up as the date the the files were copied (at least in some  mail applications (eg: Apple Mail). This program opens each message, reads the  date from the message and then sets the file time to be correct. """ # This program requires mx.DateTime.ARPA to correctly parse the date and  # time. __author__ = '(Warren Kumari)' import glob # My habit is to call logging.debug, etc. This allows overlap. import logging as Logging import mx.DateTime import optparse import os import re import sys def SetupLogging(name, level = Logging.INFO): """Creates a logger Args: The name of the logger to create (usually argv[0]) Returns: A logger object Synopsis: logging = SetupLogging(argv[0]) """ logger = Logging.getLogger(name) logger.setLevel(level) ch = Logging.StreamHandler() ch.setLevel(level) formatter = Logging.Formatter("%(name)s-%(levelname)s: %(message)s") ch.setFormatter(formatter) logger.addHandler(ch) return logger def GetDate (message): """This finds and returns the date in the supplied message. Args: message: A string (or array of strings) containing a mail message Returns: A string containing the date (or None) if it cannot be found. """ for line in message: m = re.match (r'^Date: (.*)', line) if m: date = m.group(1) try: converted_date = mx.DateTime.ARPA.ParseDateTime(date) return converted_date except ValueError, e: logging.error ("Unable to parse date %s: %s" % (date,e)) return None return None def main(): """Opens each file, parses the date and then sets the date / time""" parser = optparse.OptionParser() parser.add_option("-v", "--verbose", dest="verbose", action="store_true", help="verbose logging") parser.add_option("-d", "--directory", dest="directory", default='.', help="directory to work on") (options, args) = parser.parse_args() if options.verbose:                                  logging = SetupLogging(sys.argv[0], Logging.DEBUG) else: logging = SetupLogging(sys.argv[0], Logging.INFO) logging.debug ("Going to work in %s" % options.directory) filelist = glob.glob(os.path.join (options.directory, "*")) for filename in filelist: logging.debug ("Opening %s" % filename) infile = open(filename, "r") message = infile.readlines() date = GetDate(message) if date is not None: logging.info ("Setting date of %s to %s" % (filename, date)) os.utime (filename, (date, date)) else: logging.warn("Unable to parse date from %s" % filename) if __name__ == '__main__': main() 

Additional information