Python datetime.utcnow() Considered Harmful

By Aaron O. Ellis

Monday, June 14, 2021

The Python datetime.utcnow() classmethod is harmful and should be replaced with:

from datetime import datetime, timezone
datetime.now(timezone.utc)

Naive Time Traveling

What makes utcnow() so dangerous? Although the function gives the correct time in the UTC timezone, the returned datetime does not include a timezone:

from datetime import datetime
now = datetime.utcnow()
now.tzinfo is None # True

These timezone-less datetime instances are referred to as naive, and a naive datetime can have unexpected behavior. For instance, the timestamp() method of a datetime instance will assume that any naive datetime is local time! Although you might not notice this difference on a server set to UTC time, any timestamps will be off by your local time’s UTC offset. Here’s the offset in the US/Mountain timezone:

from datetime import datetime
datetime.now(timezone.utc).timestamp() - datetime.utcnow().timestamp() # -21600

That puts the utcnow timestamp 6 hours into the future! If you’re interacting with a service that expects a UTC-based unix timestamp, you’re likely to encounter undefined behavior. Since the naive UTC datetime is assuming that it’s local, timezones with a negative offset will generate timestamps in the future and timezones with positive offsets will be in the past.

The lack of timezone also makes comparing now() and utcnow() unpredictable. In the US/Mountain timezone, a datetime generated after another can report that it was generated before!

from datetime import datetime
in_the_past = datetime.utcnow()
datetime.now() < in_the_past # True

Although the Python documentation is clear that this function should be avoided, it has yet to be marked as deprecated, even in the upcoming 3.10 release. So double check your code!

By the way, if you want a unix timestamp, I recommend the succinct time.time():

import time
time.time() # 1623721105.8261912