
Please reconsider the Boolean evaluation of midnight - rivert
https://mail.python.org/pipermail/python-ideas/2014-March/026446.html
======
tomchristie
So ignoring the hype, here's the outcome-to-date...

The ticket was reconsidered, reopened and classified as a bug.
[http://bugs.python.org/msg212771](http://bugs.python.org/msg212771)

Nick Coghlan's dissection of the issue here:
[https://mail.python.org/pipermail/python-
ideas/2014-March/02...](https://mail.python.org/pipermail/python-
ideas/2014-March/026647.html) is pretty much perfect - wonderful piece of
technical writing!

Donald Stufft has expressed an interest in making the patch for this happen,
and _assuming_ all goes as planned this usage will raise a deprecation warning
in 3.5 and be fully fixed in 3.6.

News in brief: User raises issue. Issue gets resolved.

~~~
Grue3
Will it be resolved in 2.x though? That's what most people care about.

~~~
dtech
2.x is EOL and only remains for legacy programs. A backwards-incompatible
change won't even be considered for this branch.

------
nbouscal
If I understand the argument there correctly, the responder is saying: Nobody
should ever use this functionality, instead they should always check that the
date is not None. So, we should leave this broken, because we don't want to
break backwards-compatibility with that class of applications that nobody
should ever write.

That philosophy, taken to its logical conclusion, results in everything being
broken forever.

~~~
iandanforth
Yes. This is where Linus would step in and say "SHUT THE FUCK UP! We don't
break user space, EVER!" Developers who enter into the mindset that the users
of their code are using it "wrong" are a blight and will always be a blight.
Too bad Python has a BDFL and not a Raging DFL.

~~~
yxhuvud
Linus does NOT break backwards compatibility. That is what this change
implies.

Which doesn't mean I disagree with changing the behaviour, but I doubt Linus
would use that reason for that.

~~~
marcosdumay
It's undocummented, and very complex to use. That mail does not even have
sufficient information to understand how to use this "feature". I hightly
doubt anybody uses it outside of the datetime codebase.

I never saw Linus complaining about changing undocummented and not used
behaviour.

~~~
sirclueless
It is documented. That's the strongest argument against changing it: it's a
documented piece of user-facing behavior. The python developers are changing
it anyways because they're practical people who have decided it is more likely
that people wrote bugs into their code, than that they wrote code that
depended correctly on the falsiness of UTC midnight.

~~~
einhverfr
This is true, but if it is documented it is still sufficiently oddball to be
useless. Consider:

    
    
        Midnight UTC, timezone EST:  True
        Midnight UTC, timezone CET:  False
        Midnight UTC, timezone CST:  True
        Midnight UTC, timezone WIT:  False
        Midnight UTC, timezone PST:  True
    

Now the problem here is that it may well be documented but it is in fact
unsafe to rely on in any context. The behavior is thus irretrievably broken,
documented or not. It isn't even expert-friendly. The behavior, documented or
not, is irretrievably broken. After all timezones west of GMT will never have
false times.

If there is a use case for this behavior, I sure can't see it. Unless you like
bugs appearing when you change timezones.

------
clarkevans
INADA Naoki's argument [1] is succinct and insightful.

    
    
      I feel zero value of non abelian group should not mean
      False in bool context.
    
      () + () == ()
      "" + "" == ""
      0 + 0 == 0
      timedelta() + timedelta() == timedelta()
      time() + time() => TypeError
    

[1] [https://mail.python.org/pipermail/python-
ideas/2014-March/02...](https://mail.python.org/pipermail/python-
ideas/2014-March/026674.html)

~~~
dalke
If that were true, it would mean that dict should not support bool:

    
    
        >>> {} + {}
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
        TypeError: unsupported operand type(s) for +: 'dict' and 'dict'
        >>> bool({})
        False
    

Since I find bool(a_dict) to be very useful, and the lack of a_dict+b_dict to
be reasonable (precisely because it's non-abelian!), I conclude that logic is
cute, but not that relevant.

Those who think it this argument is pursuasive, should bool({}) raise an
exception? Or should {1:"A"}+{1:"B"} not raise an exception and instead return
... what?

Or do I misunderstand something?

~~~
mctx
{1: ["A", "B"]}?

~~~
dalke
Okay, then what about

    
    
      {1: "A"} + {}
    

Should that return {1: ["A"]}? In which case {} is no longer a zero value.

What about

    
    
      {1: "A"} + {} + {1: "B", 2: "Z"} + {1: "C"} + {}
    
    

In any case, your suggestion would break all existing code. Based on all the
StackOverflow and python-list questions, most people expect one or the other
value, and few want the two-element list containing both. (And why a list
instead of a tuple?)

Guido chose the current behavior because there is no clear-cut winner. "Refuse
the temptation to guess."

~~~
aiiane
How does {1:"A"}+{} = {1:"A"} imply that {} is no longer a zero value? 1+0=1
makes total sense...

~~~
dalke
It's not just that one expression, but when done in addition to the previous
suggested answer. That is, what is the type of the values in the summed
dictionaries?

If {1:"A"}+{} == {1:"A"} then the type of each value is unchanged from the
input list.

If {1:"A"}+{1:"B"} == {1:["A","B"]} then the type of each value (or at least
each shared value) is a 2-element list.

If both are true, then no one can write sane code which knows how deal with
the sum of two dictionaries.

That is, the insane code has to have checks for if one of the dictionaries is
empty, and if so, do one thing, otherwise do another thing. In which case, why
is this definition useful?

Therefore, either {} is the zero value, or dictionary summation produces lists
for the resulting values, but not both.

------
colanderman
I've never seen a good argument for anything beside "false" to be considered
false. Likewise for "true". Keystrokes are not a commodity for most coders,
and compilers are not dumb; just be explicit and write "!= 0" or whatever.

(And 0 == False, "" != False, but both 0 and "" are considered false? C'mon
Python, that's borderline JavaScript territory.)

~~~
rdtsc
Python has truthiness and falsiness as well.

That shortens code like:

    
    
       if not x: 
          do_something()
    

x could be 0, None, False, empty set set(), {}, [], (), '',"" or an I believe
any class or object that appropriately overrides __nonzero__() or __bool__()
methods.

It is a matter of taste, so I rather like it.

~~~
famousactress
I appreciate falsiness in python as well (especially for None, [], {},
set([]))... Interestingly I think zero is arguably the most dubious of the
falsies (not that I'd recommend changing it, for obvious backward
compatibility pain), but I can remember being burned by bugs from zero
evaluating False a number of times, yet I can't once remember being burned by
any of the other values I listed burning me.

I do hope the ticket in question is reconsidered though. Midnight is obviously
a "valid and populous instance" that shouldn't evaluate False.

~~~
maaku
"0 is False" is extremely useful to a Pythonista. Ever indexed into an array
with a boolean offset? Yes, you can do that. (False is 0, True is 1). Makes
for very succinct code.

~~~
pizza

        >>> bool() == int()
        True

------
unoti
I just got bit by this a few days ago. I was creating an event scheduling
system that uses either repeating entries with a datetime.time, or one time
entries with a datetime.datetime. I had code that said "if start_time" to see
which it was, and discovered later that midnight evaluates to false. It's not
the best idea.

~~~
einhverfr
What's actually interesting if you dig into the discussion is that the actual
behavior depends on your timezone.

Basically from UTC east to the IDL, it behaves as you describe it. Anything
west, it evaluates to true as long as it is set and there is a timezone
offset. This is because once you are at a negative offset, you subtract and
get a higher number so it will never evaluate to false with a negative offset.

Basically this current behavior is useless for anything I can imagine. You
can't use it to check for midnight UTC. You can't use it to check for whether
it is set. You can probably use to check for midnight with no timezone but add
a timezone and now your handling is messy.

This is why contracts need to be sanely written from a code contract point
ahead of time. This was apparently a developer describing how his code worked
and later calling it a contract, which is a very bad thing :-D

------
ggchappell
Ignoring Python for a bit and thinking as a designer of some hypothetical
future language: there is a nice rule given here for evaluation in a Boolean
context. I wonder whether it should be taken as a general guideline for future
languages.

The rule, in its entirety, is this:

\- Booleans are falsy when false.

\- Numbers are falsy when zero.

\- Containers are falsy when empty.

\- None is always falsy.

\- No other type of value is ever falsy.

I can think of two ways we might possibly want to alter the rule.

The first is to expand the idea of _number_ to include arbitrary groups (or
monoids?), with the identity element being falsy. So, for example, a matrix
with all entries zero might be falsy. Or a 3-D transformation might be falsy
if it does not move anything.

The second is one I have encountered in C++. There, an I/O stream is falsy if
it is in an error state. This makes error checking easy; there is one less
member-function name to remember. We might expand this idea to include things
like Python's urllib, or any object that wraps a connection or stream of some
kind.

EDIT: OTOH, there is the Haskell philosophy, where the only thing that can be
evaluated in a Boolean context is a Bool, so the only falsy thing is _False_.

EDIT 2: The comment by clarkevans (quoting a message from INADA Naoki) already
partially addressed the above group idea: "I feel zero value of non abelian
group should not mean False in bool context."

~~~
StefanKarpinski
Making anything besides booleans "truthy" strikes me as asking for a lot of
trouble for very little gain. For each of these cases, how hard is this to
write:

    
    
        x == 0
        isempty(x)
        x == nothing
    

How much clearer is the intent of that code than a truthy boolean test would
be? This clarity is all the more important in dynamically typed languages
without type annotations since the code itself doesn't give any hint what the
type of `x` might be, so the programmer doesn't know what it is the test is
really checking for.

Oh the other hand, the cost of truthiness is pretty significant. Each person
reading or writing code in a language with truthiness must remember all the
arbitrary truthiness rules that particular language uses – and they're quite
different for each of Python, Perl, Ruby, C, JavaScript, etc. As this issue
demonstrates, whenever you open the door a little, even in a generally sane
language like Python, at some point some weird decisions get made and you find
yourself with some strange corner cases like midnight being falsey. If you
wanted to know if a date was midnight, wouldn't it be easy enough to just
explicitly test that?

~~~
solutionyogi
Completely agree. C# has this right. An expression must produce a valid
boolean value, the language does not evaluate any random type as boolean based
on a set of rules.

E.g. you can't say following in C#

if(number) //number is of type integer.

if(o1) //o1 is a reference type variable.

if("string")

if(number = 10) // number = 10 is an assignment and produces 10.

You must write:

if(number == 0)

if(o1 != null)

if("string".Length > 0)

if(number == 10)

------
hyperpape
James Coglan recently pointed out that all of Python's falsy values are the
additive identity of some type. Midnight fits the mold.

This results in some weird results from an intuitive perspective, but is very
principled and elegant in other ways.

My one objection was that I don't know how None fits in.

~~~
kingkilr
Midnight is not the addittive identity of points in time. There is no additive
identity because there is no addition operation. You're confusing the ``time``
type, with the ``timedelta`` type, which represents a duration, and does have
0-minutes as an additive identity.

midnight is not, nor has it ever been, the "zero-time", it's simply a point
whose typical representation contains some 0s. It is no more Falsey than the
origin (0, 0) in the cartesian coordinate system is Falsey.

~~~
hyperpape
You win: I was confusing those things! I was thinking times were treated as a
timedelta ranging from 0 to 23 hours 59...

------
wzdd
Lots of Python objects are falsey: empty lists, empty strings, etc. So it's
never a good idea to write "if <thing>" when you mean "if <thing> is not
None".

This is pretty well-known, I thought.

~~~
randlet
A big part of it is about user expectations. I would be shocked if any
experienced Python programmer who wasn't familiar with this exact
implementation detail expected the following:

    
    
        if datetime.time(0, 0, 0):
            print "foo"
        else:
            print "bar"
    

to print "bar"

~~~
mzs
Oh man people that have used python for a while have come to expect datetime
to just be strange in general. Essentially the default is to be in your TZ
from midnight, you need to convert and there are different classes for epoch,
and some of the members seemingly willy-nilly are not there or named
differently then! And that's just the start, that's why things like arrow
exist: [http://crsmithdev.com/arrow/](http://crsmithdev.com/arrow/) time and
datetime in python show their age.

------
dec0dedab0de
Off the top of my head I can't think of a reason to check if a date exists,
but I would certainly expect midnight to be truthy if I found a reason.

~~~
danielsamuels
What about optional date fields in a Django application?

~~~
isaacremuant
you check if it's None or for the type.

------
Nanzikambe
Whilst reading that thread, I stumbled accross:

    
    
      "goto fail" is a well-known error handling mechanism in open source 
      software, widely reputed for its robusteness:
      
      http://opensource.apple.com/source/Security/Security-55471/libsecurity_ssl/lib/sslKeyExchange.c
      
      https://www.gitorious.org/gnutls/gnutls/source/6aa26f78150ccbdf0aec1878a41c17c41d358a3b:lib/x509/verify.c
      
      I believe Python needs to add support for this superior paradigm.
      
      It would involve a new keyword "fail" and some means of goto'ing to it. 
      I suggest "raise to fail":
      
      if (some_error):
         raise to fail
      
      fail:
            <error handling code>
      
      Unless there are many objections, this fantastic idea might be submitted 
      in a (short) PEP somewhere around the beginning of next month.
      
      There is some obvious overlap with the rejected "goto PEP" (PEP 3163) 
      and the Python 2.3 goto module. However, the superiority of goto fail as 
      error generation and error handling paradigm has since then been 
      thoroughly proven.
    

[https://mail.python.org/pipermail/python-
ideas/2014-March/02...](https://mail.python.org/pipermail/python-
ideas/2014-March/026581.html)

~~~
kingkilr
Python already comes with the logical conclusion of goto-fail out of the box.
There's no need to add a new special feature for it.

(There is absolutely no certificate store checking for certs by default, nor
is there any hostname checking, or any of the myriad of other checks one might
expect a reasonable TLS implementation to perform. Use the requests module.)

------
spacemanmatt
I think he understates the most powerful part of his argument.

Midnight is a value, not a special value. There is no reason why it or any
other valid time should be falsey on a daily cycle.

~~~
nostromo
Couldn't the same argument be made about zero?

~~~
einhverfr
There are times when I like the fact that zero is false in Perl.

However, usually I am happy that '0 is false' in PostgreSQL raises a type
exception.

------
josephlord
I think the interesting part is what is revealed about Python and the
difference with something like Ruby.

Python is stable[0] and places a high degree of importance on backwards
compatibility.

This behaviour is well documented (and called out for particular note). This
reinforces that it is (a) official and (b) not a bug because it is the
documented behaviour.

On the other hand Ruby (and most Ruby libraries) seem both less concerned with
backwards compatibility, have less thorough documentation[1] but are more
willing to change and improve.

There isn't a right and a wrong between these approaches although for most
things I think I would prefer something between the two. I think I generally
prefer Python in terms of syntax (Ruby is a bit too flexible with too many
ways to do things for my taste) but I do wonder if Python will be left a
little behind.

[0] Python 2/3 transition is a single big deliberate change.

[1] I have an open Rails issue that I don't know if is a bug or not because
there isn't documentation that is sufficient to compare the behaviour with so
it is a case of what feels right/wrong:
[https://github.com/rails/rails/issues/6659](https://github.com/rails/rails/issues/6659)

~~~
sanderjd
I'm gonna disagree with you! Well, I actually agree about ruby _libraries_ ,
but the ruby _standard library_ is the proper comparison here, which in my
experience manages backwards compatibility and documentation of edge cases
rather nicely. The (many) changes to the language itself since 1.8 have nearly
all happened in backwards-compatible ways. So maybe the ruby _community_ has a
more flexible attitude (I've honestly never used enough external python
libraries to have a good comparison point), but I don't think it's fair to say
that the language itself is.

~~~
josephlord
I was deliberately discussing the whole library ecosystem you could well be
right about the stability of the core of Ruby.

------
delinka
Not being a Pythonista, I have the following questions:

1) Is there a (native or custom) date type in Python? Is it an object?

2) Midnight _when_? Today? This date last year? Sure there's a "zero value"
for dates - it's the epoch for whichever platform or library you're using.

3) Why in would anyone call it a "date" if it's really a _time_?

Maybe I'm getting off into the philosophical decisions of the reptile
wranglers, but this particular debate sounds a lot like someone made a
decision long ago that had ramifications further than expected and now the
justification is engrained, things are built on it, and no one's willing to
make the 'correction.'

~~~
mildtrepidation
Python has dates, times, and datetimes[0]. They're all objects. So yes, you
could get around this by always using a datetime. However, sometimes what you
need is just a time.

[0]
[http://docs.python.org/2/library/datetime.html](http://docs.python.org/2/library/datetime.html)

------
bouk
Python has weird ideas about comparisons, I'm pretty sure it's the only
language where this is possible:
[https://eval.in/113749](https://eval.in/113749)

~~~
DasIch
That's been fixed in Python 3.

------
eq-
The only reason for midnight being a falsy value that I can think of is that
someone thought that _all_ objects should provide some functionality for
__nonzero__/__bool__.

It was a bad idea.

------
Ideka
This kind of crap is exactly the reason why I don't like doing just "if var:"
unless var is guaranteed to be a boolean.

------
pistle
Midnight UTC is zero's all the way down. Seems false to me, but I'm from the
land of C. This seems to be in line with some low level hardware or common
assembly practice across many languages.

Everyone is talking higher echelons of consideration, but what effect is there
on generated byte code or in fitting within the virtual machine's tight pants?

------
mannykannot
This offers a counterexample to the simplistic notion that 'duck typing'
results in programs that automagically do the right thing. The reality is that
duck typing does not relieve you of the responsibility of understanding the
semantics of the elements you use to construct a program from.

------
abvdasker
On the plus side, Boolean Value: Midnight would make a great CS-themed action
movie title.

------
dools
This is freakishly similar to the discussion on a PHP bug I submitted in 2006:

[https://bugs.php.net/bug.php?id=39579](https://bugs.php.net/bug.php?id=39579)

------
einhverfr
This sub discussion seemed interesting to me:
[https://mail.python.org/pipermail/python-
ideas/2014-March/02...](https://mail.python.org/pipermail/python-
ideas/2014-March/026656.html)

My first thought was this:

[http://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_el...](http://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements005.htm)

------
nas
While I agree this is surprising behavior and I wouldn't design an API this
way, it is documented behavior. From the docs:

"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"

Changing at this point would possibly break code that relied on documented
library behavior. That's not a responsible thing to do.

~~~
RyanMcGreal
Explicit is better than implicit.

Simple is better than complex.

~~~
nas
I'm not sure what that is supposed to mean in this context. Testing for "is
not None" is more explicit and avoids the trap being discussed. I don't know
how the second sentence applies, maybe "if timeval:" is simpler?

BTW, do you know the original author of the Zen of Python has posted to this
very dicussion (while also being the orginal author of said module):

[https://mail.python.org/pipermail/python-
ideas/2014-March/02...](https://mail.python.org/pipermail/python-
ideas/2014-March/026573.html) [https://mail.python.org/pipermail/python-
ideas/2014-March/02...](https://mail.python.org/pipermail/python-
ideas/2014-March/026577.html)

~~~
Pacabel
"Simplicity" is not measured in terms of key presses or characters.

The "if timeval:" case may contain fewer characters, but it's less explicit.
Being less explicit opens it up to greater ambiguity. Ambiguity is a form of
complexity. Complexity is the opposite of simplicity.

The explicit "is not None" check may require more typing, but it's far more
explicit and exact. That means it's much less ambiguous, and thus less
complex, and thus exhibits greater simplicity.

------
mark-r
In every other language I've used, a time value of 0 is used when a datetime
only contains a date and doesn't have a specific time. The existing behavior
would make sense in that context. I know Python also has a separate date
object, are the two interchangeable enough that you could mix and match
without problems?

------
Robadob
I came across a similar issue when using rails the other day, where I gave my
model a boolean field that had presence validation. The presence validation of
the boolean field fails if the bool is set to false, had me confused for a
while, but It wasn't a big enough issue for me to research/report.

------
lutusp
It seems there are two choices:

1\. Before applying a numerical value to a Boolean test, ask whether it can
ever be zero when that's not the intent of the test.

2\. Create a new rule that forbids testing numerical values as though they're
Booleans, and break nearly every program in existence.

Hmm ... wait ... I'm thinking it over.

------
njharman
Why would anyone evaluate dates in a boolean context? They are (should be)
always True.

~~~
csense
> They are (should be) always True.

This is the obvious intuition, which doesn't match the current implementation.

> Why would anyone evaluate dates in a boolean context?

I'd guess, usually to tell whether a variable has a date or None. For example,
maybe you have some kind of GUI with a form where the user is supposed to fill
in a time, and some variable somewhere in the code is set to None [1] when the
time field is blank [2], and a time object representing the time entered by
the user otherwise.

Maybe you want to require the user to fill in all fields in the form before
advancing to the next step in your program's workflow. If you like falsiness,
you might write:

    
    
        if not form.time_field:
            show_warning("You must enter a time to proceed")
            continue_current_phase()
        else:
            proceed_to_next_phase()
    

You actually intended the first line to be:

    
    
        if form.time_field is not None:
    

But the only way you'd find this bug is when a user on the East Coast
complains that the form won't accept 7:00 (UTC midnight). It will accept 6:59
or 7:01, but not 7:00. Of course you're on the West Coast, so you close their
ticket as "can't reproduce" since 7:00 works just fine for you...

[1] None is the Python equivalent of other programming languages' null (or
NULL).

[2] Or unparseable

------
jfb
Creeping semi-booleans make me very uncomfortable. But what's the alternative?
A-values and I-values? A "μ" for questions unanswerable in the type system?
Just punt and let Javascriptisms take over the world?

------
joelthelion
This is how languages die. I wasn't aware that Python had become such a
bureaucracy.

The current behavior is insane - just fix it! No need for days of discussion
on the mailing list or three-point non regression plans.

------
murbard2
Deprecate datetime and introduce datetime2 with better behavior for midnight.
Problem solved.

------
platz
boolean blindness [http://existentialtype.wordpress.com/2011/03/15/boolean-
blin...](http://existentialtype.wordpress.com/2011/03/15/boolean-blindness/)

