A More Time Zone Tolerant datetime Class (Shallow Thoughts)

Akkana's Musings on Open Source Computing and Technology, Science, and Nature.

Sat, 21 Feb 2026

A More Time Zone Tolerant datetime Class

Yesterday I signed in to the billtracker, and got an error page when trying to display my bill list:

[ ... ]
   File "/var/www/nmbilltracker/billtracker/app/models.py", line 766, in location_html
     if self.last_action_date > self.scheduled_date.replace(tzinfo=None):
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 TypeError: can't compare offset-naive and offset-aware datetimes

Python's datetime class drives me crazy.

Any given datetime object might or might not have a timezone. Those that do are called "timezone aware" or just "aware" datetimes; those without a timezone are called "unaware" or "naive". Any given function might or might not return a timezone-aware datetime. If you ever mess up and call a function that returns a timezone when you didn't expect one, or vice versa, or if a function you call changes in that respect, now you have a hidden time bomb that will crash your program the next time you do any sort of comparison with or subtraction from another datetime, and by then, you may have no idea way of finding out where the problematic time came from so you can guard against it happening again.

(In fairness, datetime classes have timezone problems in pretty much every computer language I've used. But I tend to have higher expectations for Python, and datetime's aware-vs-unaware problem seems almost as if it's deliberately designed to make things difficult for programmers.)

I could write a derived class easily enough that redefines the comparison operators < and > and == to accept either aware or naive datetimes, and treat the naive ones as local times. (Paul Ganssle has an article explaining why Why naïve times (should be) local times in Python.) But that doesn't really solve the problem, because many other functions from outside my code return datetime objects that might or might not be aware. For instance, Flask and sqlalchemy, on which the billtracker is based, store timezone-aware local times in the database, at least in postgresql (this may be something postgresql enforces; I'm not sure). But several years ago I spent weeks trying to switch all datetimes in the billtracker to aware local times to match what sqlalchemy was supposedly doing, and I could never get it working; unaware datetimes just kept showing up and breaking things. I finally gave up and rewrote it all to use unaware datetimes, which has mostly worked until that error mysteriously showed up yesterday morning.

Anyway, the point is that too much outside code returns a datetime, so I'd still have to find all of those cases, take the datetime they returned and turn them into my new derived class, and I'm bound to miss some.

When I woke up this morning, I thought I had a clever solution: "monkeypatch" the datetime class to make its comparison operators more tolerant. But after whipping up a proof of concept, I quickly learned that Python won't allow that: the datetime class is immutable, meaning you can't redefine any of its functions, no matter how broken they may seem. Sigh.

So I went back to the previous idea: a subclass, which I called
datetime_tolerant
(I know, not a great name; maybe I'll think of a better one eventually).

I haven't yet rewritten any of the billtracker code to use the new class, but I plan to. I'm sure it will take a lot of work to find all the instances where a datetime might be used and convert them to a datetime_tolerant, and I'll undoubtedly miss some; but maybe eventually this will make errors like yesterday's less common. I wish there was a way of just fixing Python's datetime instead.

Tags: , ,
[ 18:53 Feb 21, 2026    More programming | permalink to this entry | ]

Comments via Disqus:

blog comments powered by Disqus