1

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?

PyJerd
  • 11
  • 2
  • to emphasize Matt's third bullet point, "*As I understand... For a tz naive object (or one already localized in UTC), this would just be total seconds.*" is the false assumption here. One of the confusing things about Python's datetime is that naive datetime is treated as *local time* while you might be used to that being UTC from other languages. – FObersteiner Apr 06 '22 at 09:20
  • Ultimately, I'm trying to write a test that passes if the correct time zone conversion has taken place, both to and from UTC. Any wisdom on this topic? – PyJerd Apr 06 '22 at 16:43

1 Answers1

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.

Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
  • as you said, here's the output when run on a system in UTC... `Naive seconds: 946684800 timestamp: 946684800.0 object: 2000-01-01 00:00:00` `PAC seconds: 946684800 timestamp: 946713600.0 object: 2000-01-01 00:00:00-08:00` `UTC seconds: 946713600 timestamp: 946713600.0 object: 2000-01-01 08:00:00+00:00` I see that the output of .strftime("%s") is my issue. – PyJerd Apr 06 '22 at 16:27