36

I want to convert a Python datetime to an RFC 2822 datetime. I've tried these methods to no avail:

>>> from email.Utils import formatdate
>>> import datetime
>>> formatdate(datetime.datetime.now())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/email    /utils.py", line 159, in formatdate
    now = time.gmtime(timeval)
TypeError: a float is required
Ivan Chaer
  • 6,980
  • 1
  • 38
  • 48
Adam Nelson
  • 7,932
  • 11
  • 44
  • 64

5 Answers5

49

Here's some working code, broken down into simple pieces just for clarity:

>>> import datetime
>>> import time
>>> from email import utils
>>> nowdt = datetime.datetime.now()
>>> nowtuple = nowdt.timetuple()
>>> nowtimestamp = time.mktime(nowtuple)
>>> utils.formatdate(nowtimestamp)
'Tue, 10 Aug 2010 20:43:53 -0000'

Explanation: email.utils.formatdate wants a timestamp -- i.e., a float with seconds (and fraction thereof) since the epoch. A datetime instance doesn't give you a timestamp directly -- but, it can give you a time-tuple with the timetuple method, and time.mktime of course can then make a timestamp from such a tuple.

EDIT: In Python 3.3 and newer you can do the same in less steps:

>>> import datetime
>>> from email import utils
>>> nowdt = datetime.datetime.now()
>>> utils.format_datetime(nowdt)
'Tue, 10 Feb 2020 10:06:53 -0000'

See format_datetime docs for details on usage.

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
27

If you indeed want the current time, just call formatdate with no arguments:

>>> from email.utils import formatdate
>>> formatdate()
'Tue, 10 Aug 2010 20:40:23 -0000'

But, if you must pass it an argument, you want the output of time.time (a number of seconds since 01/01/1970):

>>> import time
>>> formatdate(time.time())
'Tue, 10 Aug 2010 20:41:43 -0000'

FWIW, datetime.datetime.now() returns a datetime object, which is not what formatdate expects.

Edited to add: if you already have a datetime object, you can format it appropriately for formatdate:

>>> import datetime
>>> dt = datetime.datetime.now()
>>> formatdate(float(dt.strftime('%s')))
'Tue, 10 Aug 2010 20:46:16 -0000'

Edited: Alex Martelli noted that the '%s' format string for strftime may not be portable across platforms. A possible alternative would be, as he himself suggested,

>>> formatdate(time.mktime(dt.timetuple()))
'Tue, 10 Aug 2010 20:46:16 -0000'
Ahmad
  • 69,608
  • 17
  • 111
  • 137
rbp
  • 43,594
  • 3
  • 38
  • 31
  • Interesting -- the `'%s'` format to `strftime` appears to be undocumented, and thus one of those which "happen to work on some platforms" (but not on all of them) -- unless you have docs that show it _will_ work cross-platform? – Alex Martelli Aug 10 '10 at 20:51
  • Actually, that's the format string that the Unix "date" command takes to output epoch-based time, I used it without thinking and it worked :) I'll check and get back to you (or change my answer). – rbp Aug 10 '10 at 20:55
  • Well, http://docs.python.org/library/datetime.html#strftime-strptime-behavior states that "The full set of format codes supported varies across platforms, because Python calls the platform C library’s strftime() function, and platform variations are common." And, on Linux, man 3 strftime lists '%s' as a possible format value. So I suppose you're right, it's likely not to be portable. Thanks :) – rbp Aug 10 '10 at 20:58
  • It's not portable - on Windows (Python 2.7) `'%s'`doesn't work: `ValueError: Invalid format string` – theta May 31 '12 at 16:15
  • @theta did you read my full answer? The last paragraph and code block address exactly that. – rbp May 31 '12 at 16:55
  • Yeah I know, but as no one confirmed, I did note that on Windows `'%s'`doesn't work ;) – theta May 31 '12 at 17:54
11

Python 3.3, in addition to the methods mentioned by other commenters, also added a format_datetime method, which is much cleaner:

>>> import datetime
>>> import email.utils
>>> dt = datetime.datetime.now()
>>> email.utils.format_datetime(dt)
'Thu, 09 Mar 2017 10:50:00 -0000'
FauxFaux
  • 2,445
  • 16
  • 20
1

Simple method using strftime. Python 2.7. Note: timezone = EST

>>> from datetime import datetime
>>> my_date = datetime.now()
>>> my_date.strftime('%a, %d %b %Y %H:%M:%S -0500')
'Wed, 22 Apr 2020 10:52:11 -0500'
dlink
  • 1,489
  • 17
  • 22
0

If you're using this for e.g. a HTTP header, and want GMT rather than -0000:

  • From a datetime, use format_datetime (emphasis mine):

    If it is an aware timezone with offset zero, then usegmt may be set to True, in which case the string GMT is used instead of the numeric timezone offset.

    >>> from datetime import datetime, timezone
    >>> from email.utils import format_datetime
    >>> format_datetime(datetime.now(timezone.utc), usegmt=True)
    'Wed, 27 Oct 2021 11:00:46 GMT'
    
  • From a timestamp, use formatdate:

    Optional usegmt is a flag that when True, outputs a date string with the timezone as an ascii string GMT, rather than a numeric -0000... This only applies when localtime is False.

    >>> from email.utils import formatdate
    >>> from time import time
    >>> formatdate(time(), usegmt=True)
    'Wed, 27 Oct 2021 11:05:29 GMT'
    
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437