How do I get a naive datetime object that correctly uses UTC inputs and preserves POSIX time stamp?

Viewed 1449

After being bitten badly several times by this, I remain flummoxed by the datetime module's insistence on utilizing the local time zone when creating datetime objects. What I want is a naive datetime object created from UTC date/time info, which the following code does not do:

> dt0 = dt.datetime(year=2020, month=1, day=1,hour=0,minute=0, tzinfo=None)
> print(dt0)
2020-01-01 00:00:00

> ts0 = dt0.timestamp()
> print(ts0)
1577858400.0

> dt1 = dt.datetime.utcfromtimestamp(ts0)
> print(dt1)
2020-01-01 06:00:00

As we can see, the timestamp associated with dt0 assumed that the time data provided needed to be converted from my time zone (Chicago) to UTC first.

If I explicitly specify the time zone, I get a time zone-aware object (which I don't want), which I can convert to time zone-naive by getting the time stamp and then passing it through utcfromtimestamp(). So the following works up to a point, but see the final line:

> dt0 = dt.datetime(year=2020, month=1, day=1,hour=0,minute=0, tzinfo=pytz.utc)
> print(dt0)
2020-01-01 00:00:00+00:00

> ts0 = dt0.timestamp()
> print(ts0)
1577836800.0

> dt1 = dt.datetime.utcfromtimestamp(ts0)
> print(dt1)
2020-01-01 00:00:00

> ts1 = dt1.timestamp()
> print(ts1)
1577858400.0

It changed the timestamp, apparently assuming again that dt1 is supposed to be in my local timezone!

I also thought the following might work, but it clearly doesn't, and this is where I am most baffled: the simple act of eliminating time zone info results in an unwanted timezone conversion. To me, a long-time user of datetime, this feels like a bug, not a feature.

> dt0 = dt.datetime(year=2020, month=1, day=1,hour=0,minute=0, tzinfo=utc)
> print(dt0)
2020-01-01 00:00:00+00:00

> dt0 = dt0.replace(tzinfo=None)
> print(dt0)
2020-01-01 00:00:00

> ts0 = dt0.timestamp()
> print(ts0)
1577858400.0

> dt1 = dt.datetime.utcfromtimestamp(ts0)
> print(dt1)
2020-01-01 06:00:00

Additional context: I work with geophysical data sets that are always referenced to UTC. Sometimes the time tags are actually stored as POSIX time stamps. I don't want to ever have to mess with time zones or worry about Python comparing the wrong datetime value with the times in the data sets. I increasingly wish there were a third datetime object that is neither "naive" nor "time zone aware" but rather is explicitly and exclusively UTC.

2 Answers

As asked, the answer to this is "you don't", because in Python naïve datetimes represent local time and not UTC; this is one of the reasons that you should not use utcnow or utcfromtimestamp — and there is an explicit warning about this in the documentation. If you want to tell Python that your datetime represents UTC, you attach a UTC time zone to it. You do not make it clear in your question at all what is unsatisfactory about making a datetime explicitly UTC by attaching a UTC tzinfo to it.

If you have a naïve datetime that you know represents UTC, you should simply attach datetime.timezone.utc to it, like so:

dt = dt.replace(tzinfo=datetime.timezone.utc)

If you don't want to do this for some reason, you can also get a POSIX timestamp from a naïve datetime representing UTC (but not one representing any other offset) with simple calendar arithmetic:

>>> from datetime import datetime, timedelta
>>> dt = datetime.utcfromtimestamp(12345)
>>> ts = (dt - datetime(1970, 1, 1)) / timedelta(seconds=1)
>>> ts
12345.0

This works because POSIX timestamps do not include leap seconds, so a simple calendar delta will work.

If I get you right you could use:

dt.datetime(year=2020, month=1, day=1,hour=0,minute=0, tzinfo=dt.timezone.utc).replace(tzinfo=None)

That returns:

2020-01-01 00:00:00

Is that what you want?

Related