A false midnight

Did you know...? LWN.net is a subscriber-supported publication; we rely on subscribers to keep the entire operation going. Please help out by buying a subscription and keeping LWN on the net.

Interpreted, "duck typing" languages often have some idiosyncrasies in their definitions of "truth" and Python is no exception. But Python goes a bit further than some other languages in interpreting the True or False status of non-Boolean values. Even so, it often comes as a big surprise for programmers to find (sometimes by way of a hard-to-reproduce bug) that, unlike any other time value, midnight (i.e. datetime.time(0,0,0) ) is False . A long discussion on the python-ideas mailing list shows that, while surprising, that behavior is desirable—at least in some quarters.

Python's notion of truth values defines False as various kinds of numeric zero (0, 0L, 0.0, 0j), "empty" objects ('', (), [], {}), the special value None , and, of course, False . User-defined classes can also have a False value, but many will not, which means that all objects of those classes are True . However, buried in the datetime module documentation for a time object is the following somewhat complicated explanation:

in Boolean contexts, a time object is considered to be true if and only if, after converting it to minutes and subtracting utcoffset() (or 0 if that's None ), the result is non-zero.

It all boils down to the idea that a time of 00:00 UTC (which could be something else entirely in the local time zone, of course) is False . If one were testing a variable meant to hold a time value as follows:

if current: do_something(current) # we have a valid current time

do_something()

current

None

time

if current is not None: do_something(current) # we have a valid current time

if var:

True

None

time

The code will fail toifisor is set to avalue that is exactly at midnight UTC. One can argue (and several in the thread did) that the test is wrong and should be:But "" is a common idiom in Python code—it works just fine for most objects, as they always evaluate to, socan be used as a sentinel value. But it doesn't work forobjects, at least for one second per day.

The datetime module in the standard library is a fairly simple way to represent dates, times, and timestamps (using the datetime class). The date class does not have a time associated with it, while the time class (with resolution in seconds) is undated. Oddly, a time can have a time zone associated with it (even though time zones are often date-related). A datetime has both time and date (and can include a time zone as well).

A Python bug was opened in 2012 about midnight as False , but it was closed soon thereafter as "invalid". The time class was documented to work that way and someone had created a __nonzero__() (now __bool__() ) method for time to implement that behavior. But, the behavior still didn't sit well with some. Nine months after the bug was closed, Danilo Bargen reported that the False interpretation caused problems with filters in Django templates. He asked that the bug be reopened, but nothing happened—and that's where things stood until recently.

That's when Shai Berger asked again for the bug to be reopened. That request was seconded by Andreas Pelme before moving to python-ideas at the request of Alexander Belopolsky. Berger posted a summary of the issue and asked for a reconsideration. That led to a large thread, even by python-ideas standards.

Berger's argument was twofold. First, paraphrasing Bargen and Pelme, that the current behavior is "surprising and useless" since users generally don't want special behavior at midnight. Second, his personal argument is that midnight "is not a 'zero value', it is just a value". He went on to say that midnight as False made as much sense as datetime.date(1,1,1) (the minimum date value) evaluating to False (and it doesn't).

While Paul Moore was unhappy with the practice of using Boolean tests for date validity (as it should be " is not None " in his mind), he did think the current behavior was wrong. Oscar Benjamin went further, noting that if this was new code being added, returning False for midnight in a Boolean context would never pass muster. "The question is surely whether the issue is worth a backwards compatibility break not whether the current behaviour is a good idea (it clearly isn't)."

On the other hand, Skip Montanaro is convinced that the problem is all in the application code and that the bug should remain closed. There weren't too many voices joining in with that sentiment, though Tim Peters and Belopolsky, who were both involved in the creation of the datetime module, were opposed to making any change. But Nick Coghlan posted an analysis of the current behavior that made it clear to many that it was completely inconsistent and not really tenable:

There's a great saying in the usability world: "You can't document your way out of a usability problem". What it means is that if all the affordances of your application (or programming language!) push users towards a particular logical conclusion (in this case, "datetime.time values are not numbers"), having a caveat in your documentation isn't going to help, because people aren't even going to think to ask the question. It doesn't matter if you originally had a good reason for the behaviour, you've ended up in a place where your behaviour is confusing and inconsistent, because there is one piece of behaviour that is out of line with an otherwise consistent mental model.

Coghlan had already reopened the bug, suggesting that anyone interested in seeing it fixed could post a patch to do so. The only question was how to handle deprecating the existing behavior. Python 3.4 is feature-frozen, so it is out of the picture; any change would come in 3.5 and subsequent releases. Coghlan's suggestion was to issue a DeprecationWarning whenever a time returns False in a Boolean context for 3.5, then to change the behavior so that all time values are True for 3.6. That means the change won't actually be released for roughly three years, which is too long for some.

One of those who might be in that "change it already" camp is BDFL Guido van Rossum. He posted an unequivocal statement that False time values were a mistake made a long time ago:

If I had to do it over again I would *definitely* never make a time value "falsy". The datetime module was conceived long ago, when the dangers of falsy objects that weren't clearly trying to be "like numbers" or "like collections" weren't so clear. Today I would argue that times aren't enough "like numbers" to qualify.

Furthermore, Van Rossum said, he would be inclined to fix the problem "immediately" (for Python 3.5) but would, perhaps, like to see a search for legitimate uses of the feature. Both Peters and Belopolsky noted that they had used the False midnight in programs, but nothing that was widely distributed; no other examples were mentioned in the thread. Meanwhile, multiple reports of problems stemming from the feature did come up.