Rating +1 for datetime in the category crapiest module in the Python 2.x Standard Library. The fact that datetimes in Python are time zone unaware by default, even if you parse a datetime from a string with time zone in it, sucks.

What I tried to achieve today was, I wanted to parse a ISO date like 2013-06-05T15:19:10Z and convert it to UNIX timestamp. You say simple? I thought so, too! FYI my local time zone is CEST (+0200).

First try

I skipped the first try, because I already knew this will fail, but it could be the first try for someone who does not.

from datetime import datetime

if __name__ == '__main__':
    iso_string = '2013-06-05T15:19:10Z'
    datetime_obj = datetime.strptime(
        iso_string, '%Y-%m-%dT%H:%M:%S%Z'
    )
    print datetime_obj.strftime('%s')

Result

ValueError: time data '2013-06-05T15:19:10Z' does not match format '%Y-%m-%dT%H:%M:%S%Z'

The datetime doc says:

%Z Time zone name (empty string if the object is naive).

But Z as in Zulu is not supported. Even if it would be, parsing the time zone from a string, never ever results in a time zone aware datetime object, because:

datetime.strptime(date_string, format) is equivalent to datetime(*(time.strptime(date_string, format)[0:6])).

Noticed the [0:6]? Yeah …

Second try

from datetime import datetime

if __name__ == '__main__':
    iso_string = '2013-06-05T15:19:10Z'
    datetime_obj = datetime.strptime(
        iso_string, '%Y-%m-%dT%H:%M:%SZ'
    )
    print datetime_obj.strftime('%s')

Result

1370438350

I thought, since datetime is time zone unaware, it maybe handles everything as UTC? So this should work. But …

>>> datetime.utcfromtimestamp(1370438350).isoformat()
'2013-06-05T13:19:10'

… it does not. 2 hours short! So a datetime object is aware of a time zone, my local system time zone, or at least something behind the scenes is.

Third try

I need to make the datetime object aware of the time zone. If the object already knows it’s in UTC, everything should work out fine. Btw the datetime module comes with a tzinfo base class for time zone classes, but not with a single implemented time zone. If you don’t like to implement them yourself use pytz.

from datetime import datetime, tzinfo, timedelta


class UTC(tzinfo):
    """UTC"""

    def utcoffset(self, dt):
        return timedelta(0)

    def tzname(self, dt):
        return "UTC"

    def dst(self, dt):
        return timedelta(0)


if __name__ == '__main__':
    iso_string = '2013-06-05T15:19:10Z'

    datetime_obj = datetime.strptime(
        iso_string, '%Y-%m-%dT%H:%M:%SZ'
    )
    datetime_obj = datetime_obj.replace(tzinfo=UTC())

    timestamp = datetime_obj.strftime('%s')

    print timestamp
    print '-> %s' % datetime\
        .utcfromtimestamp(int(timestamp)).isoformat()

Result

1370441950
-> 2013-06-05T14:19:10

Closer, but still 1 hour missing. Where is this hour gone? I don’t know! It’s gone!

Fourth try

I figure, since the strptime() method is wack, his homey strftime() maybe is too?! There is an other way to create a UNIX timestamp from a datetime object.

from datetime import datetime, tzinfo, timedelta
from time import mktime


class UTC(tzinfo):
    """UTC"""

    def utcoffset(self, dt):
        return timedelta(0)

    def tzname(self, dt):
        return "UTC"

    def dst(self, dt):
        return timedelta(0)


if __name__ == '__main__':
    iso_string = '2013-06-05T15:19:10Z'

    datetime_obj = datetime.strptime(
        iso_string, '%Y-%m-%dT%H:%M:%SZ'
    )
    datetime_obj = datetime_obj.replace(tzinfo=UTC())

    timestamp = mktime(datetime_obj.timetuple())

    print timestamp
    print '-> %s' % datetime\
        .utcfromtimestamp(timestamp).isoformat()

Result

1370441950.0
-> 2013-06-05T14:19:10

Nope. Tried again using utctimetuple() instead of timetuple(). But no …

1370441950.0
-> 2013-06-05T14:19:10

Fifth try

Alright then, I only got one way creating an UNIX timestamp from a datetime object left.

from datetime import datetime, tzinfo, timedelta
from time import mktime
from calendar import timegm


class UTC(tzinfo):
    """UTC"""

    def utcoffset(self, dt):
        return timedelta(0)

    def tzname(self, dt):
        return "UTC"

    def dst(self, dt):
        return timedelta(0)


if __name__ == '__main__':
    iso_string = '2013-06-05T15:19:10Z'

    datetime_obj = datetime.strptime(
        iso_string, '%Y-%m-%dT%H:%M:%SZ'
    )
    datetime_obj = datetime_obj.replace(tzinfo=UTC())

    timestamp = timegm(datetime_obj.timetuple())

    print timestamp
    print '-> %s' % datetime\
        .utcfromtimestamp(timestamp).isoformat()

Result

1370445550
-> 2013-06-05T15:19:10

Wohoooo! I did it! I successfully converted a datetime ISO string to a fully working, down to the last second correct, UNIX timestamp. So sad … let the flaming begin.

Suggestion by Jeff Epler

import time
from calendar import timegm
from datetime import datetime


if __name__ == '__main__':
    iso_string = '2013-06-05T15:19:10Z'

    timestamp = timegm(
        time.strptime(
            iso_string.replace('Z', 'GMT'),
            '%Y-%m-%dT%H:%M:%S%Z'
        )
    )

    print timestamp
    print '-> %s' % datetime.utcfromtimestamp(timestamp).isoformat()

Result

1370445550
-> 2013-06-05T15:19:10

Thanks Jeff, this one is much better!