A false midnight
lwn.net
lwn.net
However, the problem with the Python truth value of a date here goes way deeper than what folks have generally discussed and make the behavior here unsafe for any use.
When designing a module, contracts are important. You don't want to ever have to break a contract if you can avoid it. Sometimes it will be unavoidable, but you hope not. What this means is that your contracts need to be clear, precise, and meaningful, and the code needs to implement the contract.
I good contract would have read something like:
A time value is false if it represents a midnight, either when unqualified by timezone or when midnight GMT.
This gives you a specification. It gives you something you can use and count on. Maybe it is a killer feature for someone. However the implementation instead was:
A time value is false if and only if it evaluates to zero after subtracting the timezone offset if it has one.
Think about what this means when you have a negative vs a positive offset. It means that negative timezone offsets allow a date to always be true, but positive ones allow it to be false at UTC midnight. The real takeaway from this really should be the fact that API documentation should be declarative in nature and not procedural. Don't tell me how you get an answer. Tell me what the answer means. if you do the latter, then nothing can ever be fixed but probably cannot be safely used either.
The problem then is first and foremost a problem of API design and a design which is fundamentally flawed. This is why absent where the algorithm is critical to the contract it should not be mentioned. Explain what is expected and what it means, not how you get at the answer.
I haven't had my tea yet, but is this true? ISTM that if a positive offset can be false in the AM, a negative one might be false in the PM.
class Offset(tzinfo):
def __init__(self, offset):
self.offset = offset
def utcoffset(self, dt):
return self.offset
>>> bool(time(1, 0, tzinfo=Offset(timedelta(hours=1))))
False
>>> bool(time(23, 0, tzinfo=Offset(timedelta(hours=-1))))
True
Although this is kind of funny, because for time.hour, 23 and -1 should be the same thing: >>> bool(time(23, 0, tzinfo=Offset(timedelta(hours=23))))
False
I'm convinced: legacy behavior is wrong and should be changed.Can you expand on this, I don't understand (didn't read the list). Couldn't you just test for the actual time instead of a boolean if you are trying to do something at midnight?
Now many db's (maybe even most) will happily cast dates to timestamps, but they do this by filling in 00:00:00 for the time. If you have to special case these in your front end, being able to treat truncated datetimes as having no date could be quite helpful.
Now this would not be an ideal design, I think we'd all agree. It would be very remotely possible a date could be 00:00:00.000000 in time and so you'd have some false positives. Ideally we'd put a flag on the record to say the date had been truncated, but you know, there are always cases where legacy code gets in the way of sane fixes.
In an ideal world this might never be a useful feature. In the real world I could imagine a number of uses in working around systems which one cannot reasonably safely or easily modify.
Yes, you could special case check for midnight. But there are cases where "midnight is false" happens to correspond to actual behavior elsewhere.
Your two versions of the contract mean the same thing, and I am confident it is implemented such. I agree with you that the actual wording is sloppy and confusing, and your version would have been more clear. But they mean the same thing.
What this means effectively is that 1am GMT+1 would evaluate to false, but 11pm GMT-1 would evaluate to true because 23:00 + 1.00 == 24:00, and 24 != 0.
If it's 21:00 in a timezone that's GMT-3, you take 21:00 - (-3) and get 00:00, the correct GMT time. No?
Oh, I see your point, it says nothing about rollover.
Okay, I dunno. Times are confusing.
class ToBool a where
toBool :: a -> Bool
if' :: ToBool a => a -> b -> b -> b
if' cond th el = if toBool cond then th else el
And add instances like these: instance ToBool Int where
toBool 0 = False
ToBool _ = True
instance ToBool String where
toBool "" = False
toBool _ = True
Or even: instance ToBool TimeStamp where
toBool = not . isMidnight
Lisp, of course, can do similar things via Macros.Some re-education should be enough.
This is a bit of a strawman, but being able to do
class Foo(object): def __bool__(self): return random.random() > 0.5
shows how crazy you could be. If you really want something weird, put it in the if-statement
That particular bit of craziness wouldn't be possible in Haskell. (Since that would require declared side-effects, and my interface above didn't have any declared.)
- you are doing something you shouldn't
- you are doing it in the wrong language
It's particularly bad because it is the kind of "clever" unnecessary code which is unlikely to see widespread adoption, clashes with existing conventions, and will eventually be disposed of after the author has left the company to pursue other opportunities.
Don't write clever code!
And this doesn't: if the `Int` in question is the exit status of a process, then 0 should be truthy and everything else falsy.
Better to make the test explicit. If the test is at all complex you wrap it up in its own function anyway, so it's not like it makes the if expression harder to read.
Consider also the instance for `Maybe`: which of these is right?
instance ToBool (Maybe a) where
toBool = isJust
instance ToBool a => ToBool (Maybe a) where
toBool = maybe False toBoolThere's some lifting approaches that construct those functions mostly automatically for you, as wrappers around the base functions on Booleans.
This is one of my biggest pet-peeves with some languages. PHP is horrifically bad, there is a huge chart to consult if things get even remotely weird.
And the fix is always obvious, and makes sense.
Too many languages try to "help" you with features that, in the longer run, turn out to be painful in subtle ways.
I agree that for higher level languages capable of offering a true "nil" (and not a macro for 0 like NULL in C) it makes sense to make all other values "truthy".
I'm not a python coder but it surprises me it doesn't force comparisons explicitly like Java. After all "explicit is better than implicit".
That being said, what's the point of having nil and false? Seems a bit redundant to me.
> That being said, what's the point of having nil and false? Seems a bit redundant to me.
In some languages, like Common Lisp, nil and false are the same thing. However, it can be useful to separate the "this is a false value" from "this value does not exist", specially in a dynamically typed language.
For example, in Lua you get a "nil" if you access a table field that does not exist and you delete a table field if you store null to it. Before they added a separate boolean type, it was really annoying to work with boolean arrays because storing nil in the array would make it full of holes and would confuse most library functions.
if(0){
//Ruby and Clojure go here
}else{
//C and Python go here
}D: Here's a good reason to begin with: https://en.wikipedia.org/wiki/Open_world_assumption (In case wiki doeesn't make it clear enough, the CWA is squeaking mad in any situation where you cannot, in fact, determine the truth value of every proposition. Just because SQL does it doesn't make it rational.)
You can't treat non-bools as bools, but you can create casts yourself if you want to. Therefore if I want to cast a varchar as bool, I could decide what I mean by it and build the logic to do this. It would be preferable to do this with a domain though instead of a base type so as to avoid compatibility issues down the road.
>> to-logic false
== false
>> to-logic none
== false
>> to-logic off
== false
>> to-logic true
== true
>> to-logic 0
== true
>> to-logic ""
== true
>> to-logic []
== true
Like you I think this is the better way.The way this would be apparent is to simply look through a statistical sampling of uses. If 80% of the time the code is used in a boolean context, the code is actually broken, meanwhile the falsiness is used, correctly, in 0.0% of the code, then you can silently fix 80% of errors without breaking anything. Moreover, if falsiness is not explicitly relied on, but just caught kind of as an exception, then removing the false sentinel value may only cause errors by creating "exception-handling" that never runs. If this is the only "cost", while you can subtlely fix 80% of code that uses the false value (wrongly), then do it.
It would be interesting to know if anyone was using the sentinel value (correctly) and relying on it.
And how does time have a timezone without a date?
It's the small things in Python that surprise me.
I understand the desire to have truthiness to determine whether it's been set, but a minimum value is a better test than a zero value.
Testing for a "seconds since 1970" would be less bad, and perhaps if you're going to detach time from a concept of date any negative value could be considered the unset/falsey value (which is probably what people testing the value think they're testing).
How does an alarm clock app store the idea that it is supposed to go off at 6 am every day?
Arguably "in N hours from now" is an unambiguous "time". Possibly so is "in N hours from now, and then every 24 hours" is too, but most places that'll be wrong come daylight saving.
An alarm clock app (vs a regular, physical alarm clock) is possibly a bad example for "simple time". Because most apps are connected to a clock that automagically updates somehow, and will then need to "decide" if 6 am is 6 am or some other time. Maybe you just crossed one timezone and don't want to change the (subjective) time you get up, but still need to be able to tell the local time without having to calculate what the local time actually is...
As do most other, non-duck-typing languages.
Duck typing exists so you can code freely and with expression, but the obvious converse of that is it allows you to shoot yourself in the foot. From my POV it seems like people here are complaining that when they point the gun at their foot it seems to randomly go off when testing date values, and the simple answer to that would be "Well, then don't point the gun at your foot!"
IM(not so)HO the Zen of Python operates as an excellent counterbalance to the kind of pitfalls Python can lead you to, the "false midnight" being a wonderful example of said pitfalls that is completely removed when following PEP 20.
>> "Explicit is better than implicit."
Dates IRL cannot be false, so checking for falsiness is ambiguous. Perhaps you meant to check if it was "not None". And look! The wonderful expressiveness of Python allows such a thing! `if timeobj:` is a snippet that has some heavy assumptions behind it, and if I'm reading your code this is very much not explicit to me what you're trying to do.
>> "Simple is better than complex."
Dealing with dates/time will always be nasty business. Let's not add to that, and make things as simple as possible. If you are explicitly checking for midnight, perhaps a constant value set to `datetime.time(0,0,0)` would be useful for a conditional.
>> "There should be one-- and preferably only one --obvious way to do it."
There are Pythonic ways to check for `None` and there are ways to compare date time objects. The key word for me here is "obvious", since, again, the idea of a date having a boolean value is entirely foreign to the real world, and more or less a shortcut found only in computing. Don't create an edge case that you don't have to.
Now granted, the stated behavior can definitely be seen to also violate PEP 20, but simply avoiding this entire issue renders that point moot.
tl;dr: People complaining about unexpected results when writing bad Python code are barking up the wrong tree. Perhaps you should try a different language, or learn to code in a more Pythonic style? Both of these avenues would be much more profitable not only in the short term, but also the long term.
The default behaviour for objects in Python is being truthy and testing their existence with `if x` instead of `if x is not None` is definitely encouraged by the language (otherwise, why would it even let you use objects in a boolean context?). Having something that behaves like the default except once in a blue moon is just a trap and its unfair to blame it on the programmer.
Well, depends. If you try to pass a datetime.time object to the math.log function, you get
TypeError: a float is required
On the other hand, if you store your hour as a number, then it works just fine. It's still not meaningful, mind.What? It's quite a stretch to say "Python is bad" because objects have implicit truthiness and that in this one rare occasion it's implemented poorly in the standard library.