A false midnight
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:
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
The code will fail to do_something() if current is
None or is set to a time value that is exactly at
midnight UTC.
One can argue (and several in the thread did) that the test is wrong and
should be:
if current is not None:
do_something(current) # we have a valid current time
But "if var:" is a common idiom in Python code—it works
just fine for most objects, as they always evaluate to True, so
None can be used as a sentinel value. But
it doesn't work for time objects, 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:
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:
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.
While there is a danger that some legitimate programs might run afoul of the change, Van Rossum's intuition is probably right: there are likely few instances of real False midnight users, while the unexpected False problem is likely to be fairly common, with many if var: instances going undetected (or at least undiagnosed) for years. Backward compatibility is always a balancing act, but in this case it would seem that making a clean break from the existing behavior—and relatively soon—is probably the right approach.
