6

I'm trying to compare two times using the Python datetime module, but I can't seem to create a timezone-aware time object in UTC.

>>> 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>)
>>> d2 = datetime.time(10, tzinfo = EASTERN_TZ)
>>> d2
datetime.time(10, 0, tzinfo=<DstTzInfo 'America/New_York' EST-1 day, 19:00:00 STD>)
>>> d1 < d2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't compare offset-naive and offset-aware times

Is this a bug? Is there a special UTC timezone I need to use? What's going on?

Chris B.
  • 85,731
  • 25
  • 98
  • 139

3 Answers3

9

All credit to wberry for puzzling this out, but in the interest of having a concise answer, I'll summarize it here.

According to the datetime docs, when comparing two datetime.time objects: "If both comparands are aware and have different tzinfo attributes, the comparands are first adjusted by subtracting their UTC offsets (obtained from self.utcoffset())"

In the example you gave, the comparison throws the TypeError because EASTERN_TZ.utcoffset() returns None. utcoffset is None because the eastern US observes Daylight Savings Time and so the time offset from UTC depends on the date which isn't available in datetime.time.

You should use datetime.datetime objects for cross-timezone comparisons:

>>> import pytz, datetime
>>> UTC_TZ = pytz.utc
>>> EASTERN_TZ = pytz.timezone('America/New_York')
>>> d1 = datetime.datetime(2012, 1, 1, 10, 0, tzinfo=UTC_TZ)
>>> d2 = datetime.datetime(2012, 1, 1, 10, 0, tzinfo=EASTERN_TZ)
>>> d1 < d2
True
Corey Burke
  • 691
  • 5
  • 10
  • 1
    you shouldn't use `tzinfo` parameter for timezones with DST. Use `EASTERN_TZ.localize(naive_dt, is_dst=None).astimezone(pytz.utc)` to get datetime object to compare. `.astimezone()` is not necessary, but it is preferable always work with UTC time internally and convert it to other timezones only on IO – jfs Oct 03 '12 at 10:39
  • 1
    Note that in 3.3 equality comparisons between naive and aware time instances don’t raise TypeError. – kolypto Sep 07 '14 at 02:14
3

You receive the error because you are trying to measure the difference between one time object that can be tied to a particular UTC instant and another time object that is "naive" and cannot be tied to a particular UTC instant. The fix is either to make both comparands offset-aware, or both naive.

The below uses datetime objects but it's basically the same idea.

import datetime, time, pytz

EST = pytz.timezone('America/New_York')
UTC = pytz.timezone('Etc/UTC')
dt1 = datetime.datetime.fromtimestamp(time.time(), EST)
# ... time passes
dt2 = datetime.datetime.fromtimestamp(time.time(), UTC)
elapsed = dt2 - dt1
wberry
  • 18,519
  • 8
  • 53
  • 85
  • Is this a bug in Python, then? The documentation 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." If that's correct, then why is `d1` in my example saying it's offset-naive? – Chris B. May 09 '12 at 21:57
  • @ChrisB., test `UTC_TZ` and see if it returns `None` for `utcoffset`. – Mark Ransom May 09 '12 at 22:13
  • @Mark Ransom: It does not. It returns `datetime.timedelta(0)`. – Chris B. May 09 '12 at 22:16
  • Not sure why, but datetime.time objects created with EASTERN_TZ return None for utcoffset, which then causes the TypeError. – Corey Burke May 09 '12 at 22:49
  • 4
    My guess would be because for `America/New_York` the UTC offset cannot be calculated from just a `time` object; you would need a `datetime` to resolve whether daylight savings was in effect. – wberry May 09 '12 at 22:57
  • *"elapsed"* might be imprecise if there are leap seconds in between `dt1` and `dt2` e.g., `"July 1, 2012, 00:00:00 UTC" - "June 30, 2012, 23:59:59 UTC"` equals **2**, not **1**. Though it doesn't matter in most cases. – jfs Sep 04 '14 at 10:45
  • Yeah, `pytz` UTC is really Unix time, which needs to be fixed. – wberry Sep 04 '14 at 11:34
1

I'm guessing that the problem is that UTC is considered to be not-any-timezone, or "offset-naive", perhaps? I'd recommend converting everything to UTC before doing any comparisons.

You need to know timezones for inputs and outputs, obviously, but you should try to keep your internal representations all in UTC, and maybe just store the timezone of each user and convert when you need to. It will save a lot of headache in the long run.

Also, you shouldn't do it this way. It's better to use

timezone.localize(dt)

as explained here: http://pytz.sourceforge.net/#localized-times-and-date-arithmetic

  • 1
    `EASTERN_TZ.localize(datetime.time(10))` gives me `TypeError: unsupported operand type(s) for +: 'datetime.time' and 'datetime.timedelta'` – Chris B. May 09 '12 at 21:26
  • @ChrisB.: the same timezone may have different UTC offsets at different dates i.e., `tz + datetime.time()` is not enough to find out the correct UTC offset, you need `tz + datetime.datetime` assuming given time exists and it is not ambiguous. – jfs Sep 04 '14 at 11:22