get UTC timestamp in python with datetime

pat picture pat · Feb 21, 2011 · Viewed 153.9k times · Source

Is there a way to get the UTC timestamp by specifying the date? What I would expect:

datetime(2008, 1, 1, 0, 0, 0, 0)

should result in

 1199145600

Creating a naive datetime object means that there is no time zone information. If I look at the documentation for datetime.utcfromtimestamp, creating a UTC timestamp means leaving out the time zone information. So I would guess, that creating a naive datetime object (like I did) would result in a UTC timestamp. However:

then = datetime(2008, 1, 1, 0, 0, 0, 0)
datetime.utcfromtimestamp(float(then.strftime('%s')))

results in

2007-12-31 23:00:00

Is there still any hidden time zone information in the datetime object? What am I doing wrong?

Answer

vaab picture vaab · Mar 31, 2011

Naïve datetime versus aware datetime

Default datetime objects are said to be "naïve": they keep time information without the time zone information. Think about naïve datetime as a relative number (ie: +4) without a clear origin (in fact your origin will be common throughout your system boundary).

In contrast, think about aware datetime as absolute numbers (ie: 8) with a common origin for the whole world.

Without timezone information you cannot convert the "naive" datetime towards any non-naive time representation (where does +4 targets if we don't know from where to start ?). This is why you can't have a datetime.datetime.toutctimestamp() method. (cf: http://bugs.python.org/issue1457227)

To check if your datetime dt is naïve, check dt.tzinfo, if None, then it's naïve:

datetime.now()        ## DANGER: returns naïve datetime pointing on local time
datetime(1970, 1, 1)  ## returns naïve datetime pointing on user given time

I have naïve datetimes, what can I do ?

You must make an assumption depending on your particular context: The question you must ask yourself is: was your datetime on UTC ? or was it local time ?

  • If you were using UTC (you are out of trouble):

    import calendar
    
    def dt2ts(dt):
        """Converts a datetime object to UTC timestamp
    
        naive datetime will be considered UTC.
    
        """
    
        return calendar.timegm(dt.utctimetuple())
    
  • If you were NOT using UTC, welcome to hell.

    You have to make your datetime non-naïve prior to using the former function, by giving them back their intended timezone.

    You'll need the name of the timezone and the information about if DST was in effect when producing the target naïve datetime (the last info about DST is required for cornercases):

    import pytz     ## pip install pytz
    
    mytz = pytz.timezone('Europe/Amsterdam')             ## Set your timezone
    
    dt = mytz.normalize(mytz.localize(dt, is_dst=True))  ## Set is_dst accordingly
    

    Consequences of not providing is_dst:

    Not using is_dst will generate incorrect time (and UTC timestamp) if target datetime was produced while a backward DST was put in place (for instance changing DST time by removing one hour).

    Providing incorrect is_dst will of course generate incorrect time (and UTC timestamp) only on DST overlap or holes. And, when providing also incorrect time, occuring in "holes" (time that never existed due to forward shifting DST), is_dst will give an interpretation of how to consider this bogus time, and this is the only case where .normalize(..) will actually do something here, as it'll then translate it as an actual valid time (changing the datetime AND the DST object if required). Note that .normalize() is not required for having a correct UTC timestamp at the end, but is probably recommended if you dislike the idea of having bogus times in your variables, especially if you re-use this variable elsewhere.

    and AVOID USING THE FOLLOWING: (cf: Datetime Timezone conversion using pytz)

    dt = dt.replace(tzinfo=timezone('Europe/Amsterdam'))  ## BAD !!
    

    Why? because .replace() replaces blindly the tzinfo without taking into account the target time and will choose a bad DST object. Whereas .localize() uses the target time and your is_dst hint to select the right DST object.

OLD incorrect answer (thanks @J.F.Sebastien for bringing this up):

Hopefully, it is quite easy to guess the timezone (your local origin) when you create your naive datetime object as it is related to the system configuration that you would hopefully NOT change between the naive datetime object creation and the moment when you want to get the UTC timestamp. This trick can be used to give an imperfect question.

By using time.mktime we can create an utc_mktime:

def utc_mktime(utc_tuple):
    """Returns number of seconds elapsed since epoch

    Note that no timezone are taken into consideration.

    utc tuple must be: (year, month, day, hour, minute, second)

    """

    if len(utc_tuple) == 6:
        utc_tuple += (0, 0, 0)
    return time.mktime(utc_tuple) - time.mktime((1970, 1, 1, 0, 0, 0, 0, 0, 0))

def datetime_to_timestamp(dt):
    """Converts a datetime object to UTC timestamp"""

    return int(utc_mktime(dt.timetuple()))

You must make sure that your datetime object is created on the same timezone than the one that has created your datetime.

This last solution is incorrect because it makes the assumption that the UTC offset from now is the same than the UTC offset from EPOCH. Which is not the case for a lot of timezones (in specific moment of the year for the Daylight Saving Time (DST) offsets).