Posted byon under
I was going through the release notes of the new Python 3.12 version the other day, and one item caught my attention in the deprecations section:
utcfromtimestamp()are deprecated and will be removed in a future version.
If you have followed my web development tutorials you must have seen me use
utcnow() a lot, so I will clearly need to re-train myself to use an alternative, in preparation for the eventual removal of this function (likely a few years out, so no need to panic!).
In this short article I'll tell you more about why these functions are getting the axe, and what to replace them with.
What's Wrong with utcnow() and utcfromtimestamp()?
The problem that the Python maintainers have found comes from the fact that these functions return "naive" datetime objects. A naive
datetime object is one that does not have a timezone, which means that it can only be used in a context where the timezone does not matter or is already known in advance. This is in contrast to "aware"
datetime objects, which do have a timezone attached to them explicitly.
If you ask me, I think the names of these functions are misleading. A function that is called
utcnow() should be expected to return UTC datetimes, as implied by the name. I would have made it more clear that these functions work with naive time, maybe by calling them
But their names are not the problem here. The specific issue is that some Python date and time functions accept naive timestamps and assume that they represent local time, according to the timezone that is configured on the computer running the code. There is a GitHub issue from 2019 that provides some background into this, with the following example:
>>> from datetime import datetime >>> dt = datetime.utcfromtimestamp(0) >>> dt datetime.datetime(1970, 1, 1, 0, 0) >>> dt.timestamp() 18000
The example above was executed on a computer that was configured for Eastern Standard Time (EST). First,
dt is assigned a naive
datetime that is converted from the "zero" time or UNIX epoch, which is January 1st, 1970 at midnight.
When this object is converted back to a timestamp, the
dt.timestamp() method finds that it does not have a timezone to use in the conversion, so it uses the computer's own timezone, which in this example was EST (note that the EST timezone is 5 hours, or 18,000 seconds behind UTC). So we have a UNIX timestamp that originated as midnight on January 1st, 1970, and after being converted to a
datetime and back ends up as 5 am.
If you read the issue linked above, they suggest that this ambiguity did not exist in Python 2 and for that reason this was not a problem for a long time, but it now is and needs to be addressed. This sounded strange, so I had to go and check, and sure enough, the
timestamp() method that returns the incorrect UNIX time in the example was introduced in Python 3.3 and nothing similar appears to have existed back in Python 2 times.
So basically, at some point they've added a
datetime.timestamp() method (and possibly others as well) that accept both aware and naive datetimes and this was a mistake, because these methods must have a timezone to work.
These methods should have been designed to fail when a naive
datetime object is passed to them, but for some strange reason they decided that when a timezone is not provided the timezone from the system should be used. This is really the bug, but instead of fixing the broken implementations of these methods they are now trying to force people to move to aware datetimes by deprecating the two main functions that generate naive ones. They think that because a few functions assume that naive timestamps represent local times, all naive uses that are not in local time should be discouraged.
I may be missing something here, but I don't really follow this logic.
Do We Need Naive Datetimes Anyway?
To me it is clear that the Python maintainers behind this deprecation have a problem with naive datetimes and are using this supposed problem as an excuse to cripple them.
So why would you want to work with naive datetimes in the first place?
An application may be designed in such a way that all dates and times are in a single timezone that is known in advance. In this case there is no need for individual
datetime instances to carry their own timezones, since this uses more memory and processing power for no benefit, since all these timezones would be the same and it would never be necessary to perform timezone math or conversions.
This is actually very common in web applications or other types of networking servers, which are configured with UTC time and normalize all dates and times to this timezone when they enter the system. It is also a best practice to store naive datetimes representing UTC in databases. The DateTime type in SQLAlchemy represents a naive
datetime object by default, for example. This is such a common database pattern that SQLAlchemy provides a recipe for applications that use aware
datetime objects to convert these to and from naive ones on the fly as they are saved to or loaded from the database.
So yes, I expect naive
datetime objects will continue to be used, in spite of these deprecations.
Updating Your Code
Even though the deprecations are disappointing, it is important to keep in mind that it may take a few years for the functions to actually be removed. The problem is that once you switch to Python 3.12 or newer you will start seeing deprecation messages on your console and your logs, and these can get annoying. Here is an example of what you can expect to 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 in a small number of projects, and I'm already tired of seeing these warnings. So let's go ahead and look at how these two functions can be replaced.
The advice from the Python maintainers is to switch to aware
datetime objects. The deprecation warning provides a hint of what they think we should use, and the deprecation notices included in the documentation are even more specific. Here is what the notice for the
utcnow() function says:
Deprecated since version 3.12: Use
Below you can see the one for
Deprecated since version 3.12: Use
So this gives us an idea of what can be done. Here are my custom versions of the deprecated functions, with the 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))
Note that if you are using Python 3.11 or newer, you can replace
datetime.timezone.utc with a shorter
Running this script I get 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
You can tell that the first and second lines show aware
datetime instances from the
+00:00 suffix that indicates that the timezone is 00:00 or UTC. The third and fourth lines show abstract timestamps without a timezone, fully compatible with those returned by the deprecated functions.
What I like about these implementations is that they give you the choice to work with or without timezones, removing any ambiguity. Explicit is better than implicit, as the old adage says.
Become a Patron!
Hello, and thank you for visiting my blog! If you enjoyed this article, please consider supporting my work on this blog on Patreon!