deprecating datetime.utcnow() in Python

Somehow I was studying release notes new version of Python 3.12, and in the section on deprecations the following phrase caught my attention:

utcnow() And utcfromtimestamp() from datetime.datetime are outdated and will be removed in a future version.

If you’ve been following my web development tutorials, you’ve seen that I often use utcnow(); Obviously, I’ll have to relearn and use an alternative in preparation for the inevitable removal of this feature (probably in a few years, so no reason to panic!).

In this short article, I’ll go into more detail about why these features have gone under the knife and how they can be replaced.

What’s wrong with utcnow() and utcfromtimestamp()?

The problem discovered by Python maintainers stems from the fact that these functions return “naive” temporary objects. Naive object datetime does not contain a time zone, meaning it can be used in contexts where the time zone is not important or is known in advance. This is the opposite of “aware” objects datetimeto which a time zone is explicitly attached.

In my opinion, the names of these functions are confusing. You should expect that a function named utcnow() returns UTC time, as the name suggests. I would make it more obvious that these functions work with naive time, for example by calling them naive_utcnow() And naive_utcfromtimestamp().

But the problem here is not their names. The specific problem is that some date and time functions in Python take naive timestamps and assume that they describe local time according to the time zone configured on the computer where the code is running. On GitHub has an issue dated 2019which explains this and provides the following example:

>>> from datetime import datetime
>>> dt = datetime.utcfromtimestamp(0)
>>> dt
datetime.datetime(1970, 1, 1, 0, 0)
>>> dt.timestamp()
18000

This example was run on a computer using Eastern Standard Time (EST). At first dt assigned naive datetime converted from “zero” time or UNIX timethat is, midnight January 1, 1970.

When this object is converted back to a timestamp, the method dt.timestamp() finds that it doesn’t have a time zone to use in the conversion, so it uses the computer’s own time zone, which in this example was EST (note that the EST time zone is 5 hours, or 18,000 seconds, behind UTC). That is, we have a UNIX timestamp that was originally midnight on January 1, 1970, and after being converted to datetime and back it turns out to be five o’clock in the morning.

The author of the issue at the link above suggests that this ambiguity did not exist in Python 2 and for this reason was not a problem for a long time, but now it has become and needs to be resolved. This seemed strange to me, so I decided to check, and indeed, the method timestamp() returning an incorrect UNIX time in the example, was introduced by Python 3.3 and nothing similar appears to have existed in the days of Python 2.

That is, in fact, at some point the maintainers added a method datetime.timestamp() (and possibly others) accepting aware and naive temporary objects, and this was a mistake because these methods need a time zone.

These methods had to be designed to throw an error when passed a naive object datetime, but for some strange reason the developers decided that if a time zone is not specified, then the time zone from the system should be used. In fact, this is a bug, but instead of fixing broken implementations of these methods, the maintainers, having deprecated the two main functions that generate naive objects, are now trying to force people to switch to aware temporary objects. They reason that since some functions assume that naive timestamps denote local time, then all naive timestamps that do not refer to local time should be discouraged.

Perhaps I’m missing something, but I don’t quite understand this logic.

Do we even need naive temporary objects?

It is clear to me that the Python maintainers who decided to deprecate see a problem with naive temporary objects and use this perceived problem as an excuse to remove them.

So why would anyone want to work with naive temporary objects?

The application can be designed in such a way that all dates and all times are in the same time zone, known in advance. In this case, there is no need for separate copies datetime stored their own time zones because it requires more memory and computational resources, and it does not provide any benefit: all these time zones will be the same and you will never have to perform calculations with time zones or convert them.

In fact, this is very common in web applications and other types of network servers configured over time UTC and normalizing all dates included in the system and all time in this time zone. Additionally, it is best to store naive temporary objects in UTC in databases. For example, the default type DateTime in SQLAlchemy specifies a naive object datetime. This is such a common pattern in databases that SQLAlchemy provides recipe for applications that use aware objects datetimeto convert them to naive objects and back on the fly when loading and reading from the database.

So yes, I believe that naive objects datetime will continue to be used despite these deprecations.

How to update your code

While I find these deprecations disappointing, it is important to remember that it may be several years before the features are removed. The problem is that after upgrading to Python 3.12 or newer, you will start seeing deprecation messages in the console and logs, which can be annoying. Here’s an example of what you’ll see:

$ python
Python 3.12.0 (main, Oct  5 2023, 10:46:39) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from datetime import datetime
>>> datetime.utcnow()
<stdin>:1: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
datetime.datetime(2023, 11, 18, 11, 22, 54, 263206)

I’m only using Python 3.12 on a small subset of projects, but I’m getting tired of these posts. So let’s see how we can replace these two functions.

Python maintainers recommend switching to aware objects datetime. The deprecation notice has a hint about what they think you should use, and the deprecation notices in the documentation are even more specific. Here’s what the feature notice says: utcnow():

Deprecation since version 3.12: use instead datetime.now() With UTC.

Below is a notice about utcfromtimestamp():

Deprecation since version 3.12: use instead datetime.fromtimestamp() With UTC.

This gives us an idea of ​​what needs to be done. Here are my own versions of these functions, with an additional option to choose between aware or naive implementations:

from datetime import datetime, timezone
def aware_utcnow():
return datetime.now(timezone.utc)
def aware_utcfromtimestamp(timestamp):
return datetime.fromtimestamp(timestamp, timezone.utc)
def naive_utcnow():
return aware_utcnow().replace(tzinfo=None)
def naive_utcfromtimestamp(timestamp):
return aware_utcfromtimestamp(timestamp).replace(tzinfo=None)
print(aware_utcnow())
print(aware_utcfromtimestamp(0))
print(naive_utcnow())
print(naive_utcfromtimestamp(0))

It’s worth noting that if you’re working with Python 3.11 or older, you can replace datetime.timezone.utc to a shorter one datetime.UTC.

When running this script I got the following results:

2023-11-18 11:36:35.137639+00:00
1970-01-01 00:00:00+00:00
2023-11-18 11:36:35.137672
1970-01-01 00:00:00

By +00:00 it is clear that the first and second lines indicate aware instances datetime , indicating the time zone 00:00, or UTC. The third and fourth lines provide abstract timestamps without a time zone, which are fully compatible with the timestamps returned by deprecated timestamps.

What I like about these implementations is that they allow you to choose whether to work with or without time zones, which removes any ambiguity. As they say in old sayingexplicit is better than implicit.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *