This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Non-naive time comparison throws naive time error
Type: behavior Stage: resolved
Components: Versions: Python 3.2, Python 3.3, Python 2.7
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: Nosy List: Chris.Bergstresser, belopolsky, greg.weller, python-dev, r.david.murray
Priority: normal Keywords: easy, patch

Created on 2012-05-09 22:11 by Chris.Bergstresser, last changed 2022-04-11 14:57 by admin. This issue is now closed.

Files
File name Uploaded Description Edit
14766.patch greg.weller, 2012-05-10 11:55 review
14766.patch greg.weller, 2012-05-10 23:10 review
Messages (10)
msg160314 - (view) Author: Chris Bergstresser (Chris.Bergstresser) Date: 2012-05-09 22:11
The datetime module says:

An object d of type time or datetime may be naive or aware. d is aware if d.tzinfo is not None and d.tzinfo.utcoffset(d) does not return None. If d.tzinfo is None, or if d.tzinfo is not None but d.tzinfo.utcoffset(d) returns None, d is naive.

However, I can create two non-naive timezones (under this definition) which throw an exception when they're compared, because one is being considered offset-naive:

>>> import pytz, datetime
>>> UTC_TZ = pytz.utc
>>> EASTERN_TZ = pytz.timezone('America/New_York')
>>> d1 = datetime.time(10, tzinfo = UTC_TZ)
>>> d1
datetime.time(10, 0, tzinfo=<UTC>)
>>> d1.tzinfo
<UTC>
>>> d1.utcoffset(d1)
datetime.timedelta(0)
>>> d2 = datetime.time(10, tzinfo = EASTERN_TZ)
>>> d2
datetime.time(10, 0, tzinfo=<DstTzInfo 'America/New_York' EST-1 day, 19:00:00 STD>)
>>> d2.tzinfo
<DstTzInfo 'America/New_York' EST-1 day, 19:00:00 STD>
>>> d2.tzinfo.utcoffset(d2)
datetime.timedelta(-1, 68400)
>>> d1 < d2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't compare offset-naive and offset-aware times
msg160315 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2012-05-09 22:56
An equivalent test using python 3.2's datetime.timezone works fine.  Are you sure it isn't a bug in pytz?
msg160316 - (view) Author: Chris Bergstresser (Chris.Bergstresser) Date: 2012-05-09 23:44
It doesn't seem to be a bug in pytz.  AFAICT, the only methods that get called during the time comparison is "utcoffset" on the UTC timezone, and "utcoffset" on the New York timezone.

Looking closer at it, it seems that Python is calling d1.tzinfo.utcoffset(None), rather than d1.tzinfo.utcoffset(d1).  This is a problem, because the New York timezone cannot calculate the utcoffset without knowing what date it's calculating.  As a result, it returns None rather than potentially guessing wrong.
msg160317 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2012-05-10 00:07
Ah.  datetime.timezone wouldn't have that issue since it doesn't deal with DST.

The 3.3 python version of datetime calls utcoffset in the same way as you describe, and it is supposed to have the same behavior as the C version, so probably 3.2/3.3 has the bug as well.  (It does look like a bug to me.)

Note that 2.6 is in security fix only mode, so this can only get corrected in 2.7 and 3.2/3.3.

I'm surprised this hasn't been reported before.
msg160335 - (view) Author: Greg Weller (greg.weller) Date: 2012-05-10 11:55
I think this is a documentation bug.  The criteria for datetime and time objects being aware are slightly different.  A datetime object d is aware if d.tzinfo.utcoffset(d) does not return None, while a time object t is aware if t.tzinfo.utcoffset(None) does not return None (time objects call utcoffset with None while datetime objects call utcoffset with self).

This accounts for the TypeError in the original example:
>>> import pytz, datetime
>>> UTC_TZ = pytz.utc
>>> EASTERN_TZ = pytz.timezone('America/New_York')
>>> d1 = datetime.time(10, tzinfo=UTC_TZ)
>>> d2 = datetime.time(10, tzinfo=EASTERN_TZ)
>>> repr(d1.tzinfo.utcoffset(None))
'datetime.timedelta(0)'
>>> repr(d2.tzinfo.utcoffset(None))
'None'
>>> d1 < d2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't compare offset-naive and offset-aware times

It looks like this example is flawed according to http://pytz.sourceforge.net/ : "Unfortunately using the tzinfo argument of the standard datetime constructors ‘’does not work’’ with pytz for many timezones. It is safe for timezones without daylight savings trasitions though, such as UTC."  (If you think about it, the error still makes sense: the UTC time is aware and the eastern time is naive -- any DST time object has to be naive -- but this has more to do with pytz).

It doesn't make sense to have time objects pass self to utcoffset because utcoffset expects a datetime object.  You shouldn't be able to infer what the UTC offset is from a time object unless the offset is constant, in which case you don't even need the time object.  If the UTC offset isn't constant, you need the date, which a time object doesn't have.  I think this is the logic behind having datetime objects call utcoffset(self) and time objects call utcoffset(None).

I've attached a small patch for the documentation.
msg160342 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2012-05-10 12:53
My, that's embarrassing.  I somehow entirely missed the fact that we were dealing with times and not dates here.

What you say makes sense to me, and the doc patch looks good.
msg160351 - (view) Author: Chris Bergstresser (Chris.Bergstresser) Date: 2012-05-10 15:14
That patch fixes the documentation there, but the description at the top of the distinction between naive and aware time objects at the top datetime module is still wrong.  Here it is:

-----------------

There are two kinds of date and time objects: “naive” and “aware”. This distinction refers to whether the object has any notion of time zone, daylight saving time, or other kind of algorithmic or political time adjustment. Whether a naive datetime object represents Coordinated Universal Time (UTC), local time, or time in some other timezone is purely up to the program, just like it’s up to the program whether a particular number represents metres, miles, or mass. Naive datetime objects are easy to understand and to work with, at the cost of ignoring some aspects of reality.

------------------

The distinction is not whether the object has any notion of "time zone, daylight saving time, or other kind of algorithmic or political time adjustment", but instead whether, in the context it's being used, it can calculate an offset to UTC.  A naive time can be used to create an aware datetime.
msg160380 - (view) Author: Greg Weller (greg.weller) Date: 2012-05-10 23:10
I agree that the language in the quoted paragraph makes it sound as if any object with a non-None tzinfo is aware, which isn't the case.  I've changed the first couple sentences to, I think, better reflect what characterizes an object as being aware.
msg160676 - (view) Author: Roundup Robot (python-dev) (Python triager) Date: 2012-05-15 02:33
New changeset 28ed782949f9 by R David Murray in branch '3.2':
#14766: Add correct algorithm for when a 'time' object is naive.
http://hg.python.org/cpython/rev/28ed782949f9

New changeset b1e03e863386 by R David Murray in branch '3.2':
#14766: Reflow the altered paragraphs.
http://hg.python.org/cpython/rev/b1e03e863386

New changeset 6ff172db8114 by R David Murray in branch 'default':
Merge #14766: Add correct algorithm for when a 'time' object is naive.
http://hg.python.org/cpython/rev/6ff172db8114

New changeset 5d1b32e33e67 by R David Murray in branch '2.7':
#14766: Add correct algorithm for when a 'time' object is naive.
http://hg.python.org/cpython/rev/5d1b32e33e67

New changeset a002638919d8 by R David Murray in branch '2.7':
#14766: Reflow the altered paragraphs.
http://hg.python.org/cpython/rev/a002638919d8
msg160678 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2012-05-15 02:38
I reformatted the first hunk a bit, hopefully to make it even a little bit clearer.  We're still punting the actual definition to the later section, but I think that's appropriate.

Thanks for the patch, Greg.
History
Date User Action Args
2022-04-11 14:57:30adminsetgithub: 58971
2012-05-15 02:38:05r.david.murraysetstatus: open -> closed
resolution: fixed
messages: + msg160678

stage: needs patch -> resolved
2012-05-15 02:33:48python-devsetnosy: + python-dev
messages: + msg160676
2012-05-10 23:10:38greg.wellersetfiles: + 14766.patch

messages: + msg160380
2012-05-10 15:14:47Chris.Bergstressersetmessages: + msg160351
2012-05-10 12:53:50r.david.murraysetmessages: + msg160342
2012-05-10 11:55:28greg.wellersetfiles: + 14766.patch

nosy: + greg.weller
messages: + msg160335

keywords: + patch
2012-05-10 00:07:56r.david.murraysetkeywords: + easy

stage: needs patch
messages: + msg160317
versions: + Python 2.7, Python 3.2, Python 3.3, - Python 2.6
2012-05-09 23:44:33Chris.Bergstressersetmessages: + msg160316
2012-05-09 22:56:33r.david.murraysetnosy: + r.david.murray, belopolsky
messages: + msg160315
2012-05-09 22:11:09Chris.Bergstressercreate