-
-
Notifications
You must be signed in to change notification settings - Fork 31.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFE: change bool(datetime.time(0, 0, 0)) to evaluate as True #58144
Comments
midnight is represented by datetime.time(0,0,0). However, this time (unlike all other valid times, including datetime.time(0,0,1)) evalutes to false in if conditions: import datetime
if datetime.time(0,0,0):
print "datetime.time(0,0,0) is not a bug!"
else:
print "datetime.time(0,0,0) is a bug!"
if datetime.time(0,0,1):
print "datetime.time(0,0,1) is not a bug!"
else:
print "datetime.time(0,0,1) is a bug!" |
I'm updating the versions to the ones that I've actually tried it on - this is not an exhaustive search at this point. |
I don't think I would have ever thought of testing a datetime for its truth value. But the behavior you observe is consistent with the rest of Python: 0 is false. I wonder if this is by design or by accident. |
Right. I've updated my code to be more correct: instead of: if not start_time:
start_time = default_time it now reads: if start_time is None:
start_time = default_time which operates correctly and works fine for my case, I just thought it was odd that one time out of all of them evaluates to False. I too wonder if it's by design or not. It's definitely not documented if it is by design. |
It must be by design -- someone has implemented a __bool__ (formerly __nonzero__) method; otherwise all objects would be true. |
BTW, "being a valid time" is not a good argument: 0, "" or False are all valid instances of their types. |
This is by design. I don't have a reference, but I do remember this being discussed. Suggestions for improving the documentation are welcome. |
From the docs, at: http://docs.python.org/library/datetime.html#time-objects """ |
Although I find it odd, you are all correct. It is documented. (I don't know how I missed that when I read those docs looking for that exact documentation). It is consistent with the rest of python. Do I close this? Do you guys? I'm fine with closing it. Thanks and sorry for the noise. |
It is odd, but really no odder than "zero values" of other types evaluating to false in Boolean contexts ;-) Closing as "invalid". |
Yeah - good point, and I agree. |
I disagree, I think this bug should be reopened and fixed. A use case that I just ran into: I'm using a Django form with time fields that aren't required, but that are only valid in combination (if there's a open time there has to be a close time). if not (self.closed or (self.open_time and self.close_time )):
raise ValidationError("Invalid times") This leads to a Validation Error when using the times 12:00 and 00:00. Of course, this case is debatable and can be worked around by using I'm using the following widespread pattern inside my templates:
The "default" filter used in this case displays the string "-" if the value on the left side of the | symbol evaluates to False. That makes sense in almost all of the cases. In the case of the (By the way, through these experiments I also found a bug in Django's date formatting template function caused by this inconsistency, which I will report separately.) I agree that casting time objects to a boolean value doesn't make much sense. But especially because of that, inconsistencies in the resulting boolean value should NOT exist. Yet another argument for this is that in many regions midnight isn't considered 00:00, but 24:00, which would obviously not evaluate to False. Please fix this. Otherwise it will lead to a whole lot of weird bugs in software using the datetime library. |
Just got bit by this. Tim Peters said: """ I disagree. Midnight is not a "zero value", it is just a value. It does not have any special qualities analogous to those of 0, "", or the empty set. Time values cannot be added or multiplied. Midnight evaluting to false makes as much sense as date(1,1,1) -- the minimal valid date value -- evaluating to false (and it doesn't). It makes perfect sense for timedelta(0) to evaluate to false, and it does. time is different. Also, while I appreciate this will never be fixed for Python2, the same behavior exists in Python3, where there may still be room for improvement. I second Danilo Bergen's request. Please reopen. |
I agree with Danilo and Shai -- this behavior very surprising. I deal with datetimes a lot, and this bug has bitten me a number of times. I cannot really think of a single case where "if timeobj:" is useful with the current behavior. It results in a check for "is timeobj midnight or false?" Would that ever be useful in practice? If you are indeed checking for midnight, surely "if timeobj == time(0, 0):" would be the most explicit and obvious way to do it? |
Please bring your case to python-ideas. All developers who commented on this issue agree that it is invalid. |
Rewording the issue title and reopening based on the python-ideas thread. The rationale for making this change is that the current behaviour converts a stylistic problem in checking values against a sentinel via "bool(value)" instead of "value is not None" into a subtle data driven behavioural bug that only occurs exactly at midnight UTC. If someone wants to write the patch to deprecate this behaviour in Python 3.5 (reporting a deprecation warning whenever midnight is interpreted as False, perhaps suggesting the use of "is" or "is not" instead), and then actually change the behaviour in 3.6, I don't believe we should actively oppose them from doing so. |
Discussion in bpo-20855 made me realise that any deprecation warning should explain how to convert a time object to "seconds since midnight". That model of time of day is the most likely origin of the current behaviour, and explicit conversion to that form would be the easiest way to avoid the deprecation warning in any cases where the current behaviour is actually considered desirable. |
I like the current behavior. We have modulo arithmetic here and |
It's not modulo arithmetic. |
Unix time modulo 86400 gives the number of elapsed seconds in a day |
Yes, but a datetime.time object is definitely not UNIX time. |
I agree that having midnight evaluate to false is completely unexpected and unintuitive. I can imagine situations where it bites people in production use in the uncommon case that a time is exactly equal to midnight. |
On 06.03.2014 10:30, Antoine Pitrou wrote:
datetime values with the time set to midnight are *very* common in practice. |
Donald did some additional testing, and it turns out that it is |
It's actually a bit worse than that Nick. It's midnight UTC, as long as the UTC offset is Positive or Zero. This is because the way the check is implemented is naive. It's implemented as: Take the time portion sans the tzinfo and convert to minutes, then take the utc offset and convert that to minutes, then subtract the second from the first and if that is zero it is False. So if you take -5 for instance (my own timezone!) the equation to determine when the "False" time is would look like: x - (-5 * 60) = 0 So we'd need a time that can be represented as -300 minutes. Since times can not be negative that means for a timezone aware time it is impossible for something with a negative UTC offset to ever be False while for a zero or positive UTC offset it'll be False at UTC midnight. |
I wrote up a longer post on python-ideas regarding the problems that the current behaviour poses when it comes to inferring a correct mental model for datetime.time(): https://mail.python.org/pipermail/python-ideas/2014-March/026647.html As part of that, it's also worth noting the current behaviour in boolean context is effectively shorthand for: import datetime as dt
naivemidnight = dt.time(0, 0)
utcmidnight = dt.time(0, 0, tzinfo=dt.timezone.utc
if x in (naivemidnight, utcmidnight):
... So if the current boolean behaviour is deprecated and removed, it is easily reproduced through equality checks. It may also make sense to offer an API to easily calculate seconds since midnight, but that would be a separate issue. |
More proposals from the thread (paraphrased):
Independent observation:
Use case presented for the current behaviour:
|
Current status of thread discussion (yes, I'm biased, and that shows in the phrasing below): Arguments raised for status quo:
Arguments in favour of changing the behaviour:
|
I'm not sure what a "structured data type" is, but in my mind the original poster's construct is more than a style error. He was using None as a sentinel, but not explicitly testing for its presence. The same error would be present if he used None as a sentinel value where the range of possible values was the set of all integers. If there are problems with the definition of "false time" such that there are some combinations of time and timezone where UTC midnight is not zero, I would prefer to correct them. Further, playing the devil's advocate, if you dispense with any false elements of time objects, why not simply zero out the nb_nonzero slot in time_as_number? Once that's gone, the time_as_number structure is all zeros, so the tp_as_number slot in PyDateTime_TimeType can be cleared. |
Structured data is just a shorthand way of referring to any Python object The problem datetime.time is both that its current behaviour is internally I suggest it makes far more sense to instead eliminate the quirky behaviour |
Ah, interesting. I just wrote a program last month where I was baffled that time didn't support arithmetic, and had to dodge painfully through datetime instances to do the arithmetic. I asked about it on IRC and someone said it was because arithmetic on times was ambiguous because of timezones, and I just accepted that rather than wonder why it hadn't been implemented. Otherwise I'm pretty sympathetic to the RFE, but I'd really like time arithmetic to work, so I guess I'd have to be -1 in that case, wouldn't I? |
Adding times of the day sounds as well-defined to me as adding |
As does adding dates. I'm talking about timedelta arithmetic, just like for datetimes. I believe that still requires modulo arithmetic :) |
|
What is wrong with adding temperatures? Climate people do it all the time when computing the averages. |
[Nick]
aware_datetime_object.time() already returns a naive time object. The thorny question is what .timetz() should return - but if aware time objects _were_ deprecated, .timetz() itself would presumably be deprecated too.
The class constructor datetime.combine(date_object, time_object) makes it easy to combine any two date and time objects into a datetime object. |
If no one else has gotten to this in the next six months or so, I will. :) |
So is the plan to deprecate this in 3.5 and remove in 3.6? If so, the question is where should the deprecation be thrown? |
There is no plan, other than the BDFL asking for a survey of what is happening with code that relies on this in the real world. FTR I'm completely against this change. I see no reason to change something that's been in use for maybe nine years and does what it's documented to do, based on somebody's expectations, failure to read the documents and buggy code. To me it would be a nail in the coffin of Python's very conservative, and to me extremely proper, view of maintaining backward compatibility. |
To be specific, Guido said that if this 3.0 or 3.1 he'd be all for changing it, and the only question in his mind is how safe it is change. And that his intuition is that it's a nuisance feature and few people have actually relied on it and that he'd be OK with fixing (and breaking) it in 3.5, perhaps after a thorough search for how often the feature is actually relied on and how legitimate those uses are. (See the full response at https://mail.python.org/pipermail/python-ideas/2014-March/026785.html) |
Mark, we kinda proved we're willing to break backwards compatibility in the name of improving usability when we embarked down the path of creating Python 3 and an associated transition plan from Python 2, rather than just continuing to develop Python 2. Compared to some of the backwards compatibility breaks within the Python 2 series that were handled using the normal deprecating cycle (removing string exceptions, anyone?), this one would be truly trivial. |
This behavior conflicts with the other major classes, datetime.date and datetime.datetime. The ostensible reason for this falsy behavior is that midnight represents a fundamental zero point. We should expect to see similar zero points that evaluate to False for the other two classes. However, they do not include such falsy behavior. In [2]: bool(datetime.datetime(datetime.MINYEAR, 1, 1)) Why don't these classes have any sense of zero at their minimums? datetime.time.__bool__ should be dropped if for nothing more than consistency. |
New changeset 89aa669dcc61 by Benjamin Peterson in branch 'default': |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: