'python3 datetime.timestamp() not giving correct POSIX time

I'm debugging some datetime functionality, specifically converting between local time and UTC. The program I'm working on will only be running on Linux and Mac but I would like to see a cross-platform solution. I'm using datetime.timestamp() to generate a utc timestamp in seconds to be stored in a database but am noticing some very erratic behavior when converting to and fro.

According to the python docs, datetime.timestamp() is supposed to return a float representing POSIX(UTC) time as seconds since the epoch. As I understand... For a tz naive object (or one already localized in UTC), this would just be total seconds. For a tz aware object, this time should be automatically be converted from the given tz to utc.

To test this behavior, I create a tz-naive datetime object at Jan, 1 2000. This time is then localized as US/Pacific and also converted to UTC with .astimezone(). Here's some setup code:

import datetime, time
from pytz import timezone

tz_pac = timezone("US/Pacific")
tz_utc = timezone("UTC")
start_tuple = (2000, 1, 1, 0, 0, 0)
naive_time_obj = datetime.datetime(*start_tuple, tzinfo = None)
pac_time_obj = tz_pac.localize(naive_time_obj)
utc_time_obj = pac_time_obj.astimezone(tz_utc)

naive_seconds = int(naive_time_obj.strftime("%s"))
pac_seconds = int(pac_time_obj.strftime("%s"))
utc_seconds = int(utc_time_obj.strftime("%s"))

print("Naive\tseconds:", naive_seconds, "\ttimestamp:", naive_time_obj.timestamp(), "\trepr:", naive_time_obj)
print("PAC\tseconds:", pac_seconds, "\ttimestamp:", pac_time_obj.timestamp(), "\trepr:", pac_time_obj)
print("UTC\tseconds:", utc_seconds, "\ttimestamp:", utc_time_obj.timestamp(), "\trepr:", utc_time_obj)

And here's what I'm getting:

Naive    seconds: 946713600     timestamp: 946713600.0     repr: 2000-01-01 00:00:00
PAC      seconds: 946713600     timestamp: 946713600.0     repr: 2000-01-01 00:00:00-08:00
UTC      seconds: 946742400     timestamp: 946713600.0     repr: 2000-01-01 08:00:00+00:00

Output for the naive object makes sense as there is no timezone info to convert with. For the PAC localized object, I expect timestamp to be tz converted from seconds, but instead they are equal. For the UTC localized object, I expect seconds and timestamp to be equal, but a conversion has taken place. What am I missing?



Solution 1:[1]

A few things:

  • Unix Timestamps (aka POSIX Timestamps) are always in terms of UTC (on any platform/language) because they're representing a distinct instant in time. Converting the representation to another time zone doesn't change that instant.

  • %s should not be used in Python. It's influenced by your local time zone. See this answer for more details.

  • Python's timestamp method will correctly give the instant for aware datetime instances, but for naive instances, they are assumed to be in terms of local time. Thus a local-to-utc conversion is done before calculating the timestamp. This is covered in the docs which says:

    Naive datetime instances are assumed to represent local time and this method relies on the platform C mktime() function to perform the conversion.

    In your case, the naive_time_obj.timestamp() only matches the other timestamps because your local time zone is likely also Pacific time. If you ran this code under a different local time zone, you'd get a different value.

  • In the last column of your output, all three values are showing the correct representation, and thus the conversion occurred properly.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Matt Johnson-Pint