Why is datetime.utcnow deprecated?

Again, IMO timezoned datetimes in Python standard library have bigger footguns than datetime.datetime.utcnow().

datetime.datetime.utcnow() has real world use cases as many datetime systems are modeled without time zones but are assumed to be UTC. If you expected tzinfo of the returning object to be UTC it is easy to see that it is None and you can fix that.

Whereas timezoned standard library datetimes errors are much more subtle and harder to understand:

>>> paris = zoneinfo.ZoneInfo("Europe/Paris")
>>> now = datetime.datetime(2023, 3, 25, 22, tzinfo=paris)
>>> then = datetime.datetime(2023, 3, 26, 7, tzinfo=paris)
>>> then - now
datetime.timedelta(seconds=32400)

In many cases users would likely assume that if you have two datetimes with real time zones you can take the difference between them and get the correct answer (which in this case is 28800 seconds), because otherwise what is the point of being able to add real time zones to a datetime object? So errors are going to silently propagate and be difficult to reason about.

Again, I would advise users to replace datetime.datetime.utcnow() with datetime.datetime.now(datetime.UTC).replace(tzinfo=None).

1 Like

Since whenever was mentioned here, I wanted to chime in

whenever seems to be doing something weird in its add method:

While I definitely could have documented this better (the docstring now just contains a link to the docs), this behavior is consistent with existing standards (RFC5545 iCal) and carefully designed datetime libraries in other languages (java.time, NodaTime, Temporal):

  • Years, months, weeks, and days are considered as operating on the calendar only. Therefore “1 day later” will keep the same wall-clock time regardless of a DST transition overnight. This is what you’d expect when moving a meeting by a few days.
  • Hours, minutes, seconds, etc. are considered “exact” units. “3 hours later” should always measure to be three hours on your stopwatch, regardless of what the wall clock says. This is also what you’d expect when agreeing to meet a friend “3 hours later”. You wouldn’t want to show up 1 hour earlier/later because of DST
  • When given multiple units to add, they are added from largest to smallest (i.e. months, then days, then hours/minutes/seconds etc.)

# But it’s commutative, right?

Mixed arithmetic of calendar units (months, days, etc.) and exact units (hours, seconds, etc) cannot be commutative (or reversible)—in a world with Daylight Saving Time, at least.

If you’re curious, I did put a lot of thought into making arithmetic as consistent as possible (see here and here). But my conclusion is that datetime arithmetic is simply one of those topics that cannot be understood fully at first glance. It’s up to library developer to:

  1. make the API such that it’s at least hard to do the wrong thing (such as accidentally ignoring DST)
  2. Provide in-depth documentation and clarity of semantics

The conclusion is that, if you use a single datatype (timedelta) or a single method (add) to expose both semantics, you invariably end up with confusing or unexpected results

Agree. However, I’d argue that “practicality beats purity” here. As I mentioned at the beginning: there are agreed-upon semantics for this in the iCal standard, Java, C#, and Javascript.

9 Likes

Here is a real world example of a library hitting this footgun: Fix calculating remaining time across DST changes by imrivera · Pull Request #9669 · celery/celery · GitHub

2 Likes