The pitfalls of datetime

Python’s datetime module first appeared in Python 2.3, back in 2003. That it has remained usable for over twenty years is remarkable, in a problem domain this tricky: Java and JavaScript both ended up replacing their date-time APIs wholesale, while Python never needed a wholesale replacement.

It does, however, have sharp edges which regularly trip up even experienced developers. Below are the most impactful ones, and what whenever does instead.

Note

None of this is a condemnation of datetime. It has been carefully maintained and adapted over the years—all while preserving backwards compatibility. “Pitfall” is a subjective term: what follows is simply a catalog of the places where the design makes certain mistakes easy to make.

One type, two meanings

One class for two incompatible concepts, so annotations can’t tell them apart

One type for two incompatible concepts
operators ignore DST

Eight hours after 10pm isn’t always 6am, but + thinks it is

+ ignores DST
“Naive” is overloaded

Sometimes the system time zone, sometimes UTC, sometimes neither

“Naive” is interpreted differently in different places
Silent ambiguity

Times that happen twice—or never—are resolved without a word

Ambiguity is resolved without a word
equality ignores fold

Identical moments can compare unequal, and distinct ones equal

== ignores fold
timezone isn’t a time zone

Several time zone classes to choose from; the obvious one is wrong

timezone isn’t a time zone
Casing depends on the filesystem

The same time zone ID can load on your laptop and fail in production

Time zone casing is platform-dependent
Quietly uses the system time zone

The result depends on your machine’s configuration

The system time zone is used implicitly
Broken date inheritance

A subclass that can’t be compared with its own base class

datetime inherits from date—and it breaks
timedelta.seconds footgun

A remainder that looks like a total—right up until it doesn’t

timedelta.seconds footgun