dateutil and pytz give different results
Asked Answered
C

1

18

I have an issue comparing outputs with dateutil and pytz. I'm creating a aware datetime object (UTC) and then converting to a given time zone, but I get different answers. I suspect that dateutil sometimes gives wrong results because it has problems taking into account daylight saving time (at least, I read a comment about it) but I can't find confirmation or a fix to that issue. This is the code:

import dateutil

u = dateutil.tz.tzutc()
date1 = datetime.datetime(2010, 5, 2, 11, 10, tzinfo=u)
# 2010-05-02 11:10:00+00:00

u2 = dateutil.tz.gettz('America/Chicago')
date2 = datetime.datetime(2010, 5, 2, 11, 10, tzinfo=u2)
# 2010-05-02 11:10:00-05:00


import pytz
u = pytz.timezone('UTC')
date1 = datetime.datetime(2010, 5, 2, 11, 10, tzinfo=u)

# 2010-05-02 11:10:00+00:00
u2 = pytz.timezone('America/Chicago')
date2 = datetime.datetime(2010, 5, 2, 11, 10, tzinfo=u2)

# 2010-05-02 11:10:00-06:00

So, what could be the problem here?

UPDATE:

I just tried this:

print u2.normalize(date1.astimezone(u2))
# 2010-05-02 06:10:00-05:00

So pytz needs normalize to consider DST?

UPDATE 2:

It seemed as if pytz and dateutil don't give the answer for America/Argentina/San_Luis but this works:

import pytz, dateutil, datetime

now = datetime.datetime.now() 

for zone in pytz.all_timezones:
    utc_dateutil = dateutil.tz.tzutc()
    utcdate_dateutil = datetime.datetime(now.year, now.month, now.day, now.hour, now.minute, tzinfo=utc_dateutil)
    zone_dateutil = dateutil.tz.gettz(zone)
    newzone_dateutil = utcdate_dateutil.astimezone(zone_dateutil)
    
    utc_pytz = pytz.timezone('UTC')
    utcdate_pytz = datetime.datetime(now.year, now.month, now.day, now.hour, now.minute, tzinfo=utc_pytz)
    zone_pytz = pytz.timezone(zone)
    newzone_pytz = utcdate_pytz.astimezone(zone_pytz)
    assert newzone_dateutil == newzone_pytz

Am I missing something?

Cade answered 19/12, 2012 at 2:27 Comment(5)
11:10 turning into 12:57 is mighty weird. Something corrupted with your America/Chicago time zone? Try it on another machine? I can't reproduce this behaviour.Clemons
Sorry. Typo. Initially I used another datetime object as example. Fixed now. Notice that in one timezone it says 05:00 and in the other it says 06:00. That's the problem.Cade
Oh, well then in that case I don't know why. +1 good question :-) -05:00 definitely seems like the correct one (standard time zone -06:00 with DST added)Clemons
Thanks for verifying which one is correct. Just now, I did this: u2.normalize(date1.astimezone(u2)) and got again -05:00. Maybe pytz needs normalize to take into account DST. I will update my question with that info.Cade
pytz is quite stupid when you apply it without using localize. It just uses the first thing in the database for that zone, which in some cases is complete nonsense - see #11442683. It certainly doesn't account for DST.Antipodes
M
16

Edit: The discrepancy discussed below no longer exists when using

>>> dateutil.__version__
'1.5'

>>> pytz.__version__
'2012c'

The pytz module warns,

this library differs from the documented Python API for tzinfo implementations; if you want to create local wallclock times you need to use the localize() method

and further on

This library only supports two ways of building a localized time. The first is to use the localize() method provided by the pytz library.

In [61]: u4 = pytz.timezone('America/Chicago')
In [62]: print(u4.localize(datetime.datetime(2010, 5, 2, 11, 10)))
2010-05-02 11:10:00-05:00

The other way is to use the astimezone method, which is used to convert a timezone-aware datetime into another timezone-aware datetime.

And to be completely explicit, it warns against constructing a timezone-aware datetime using the tzinfo argument:

Unfortunately using the tzinfo argument of the standard datetime constructors ‘’does not work’’ with pytz for many timezones.


Let's test the hypothesis that

datetime.datetime(year, month, day, hour, minute, tzinfo = dateutil_tz)

equals

pytz_tz.localize(datetime.datetime(year, month, day, hour, minute))

with this code:

import dateutil.tz
import datetime
import pytz

now  = datetime.datetime.now()

for name in pytz.all_timezones:
    dateutil_tz = dateutil.tz.gettz(name)
    pytz_tz = pytz.timezone(name)
    dateutil_date = datetime.datetime(
        now.year, now.month, now.day, now.hour, now.minute, tzinfo = dateutil_tz)
    pytz_date = pytz_tz.localize(datetime.datetime(
        now.year, now.month, now.day, now.hour, now.minute))

    try:
        assert dateutil_date.isoformat() == pytz_date.isoformat()
    except AssertionError:
        print(name)
        print(dateutil_date.isoformat())
        print(pytz_date.isoformat())           

The code yields:

America/Argentina/San_Luis
2012-12-18T22:32:00-04:00 <-- dateutil datetime
2012-12-18T22:32:00-03:00 <-- pytz's datetime

So my hypothesis was wrong: dateutil and pytz return different results.

So which one is correct? I'm not really sure, but according to this website, currently,

America/Argentina/San_Luis time zone offset is: 
UTC / GMT -03:00 hours

so it appears pytz is correct.

Marquita answered 19/12, 2012 at 2:49 Comment(8)
Excellent. Are there similar warnings in dateutil? I have seen that many people prefer pytz so I thought it provided better support.Cade
This is confusing. I can't find that time zone in this list en.wikipedia.org/wiki/… and after trying many of them, each of them gave the same result in dateutil and pytz. America/Argentina/San_Luis is recognized and indeed, it shows discrepancies.Cade
Yes, I am confused too. I think both dateutil and pytz read the same database (/usr/share/zoneinfo on Ubuntu), so why there should be only one timezone with a discrepancy is rather odd.Marquita
There is a bit of information on America/Argentina/San_Luis here, which also indicates the offset is -03:00.Marquita
Yes, it looks like it's listed in the Olson database. Hmm, it gets weirder and weirder.Cade
I think this could be due to some issue in some representation of dateutil or pytz because I tested all zones and all worked, including America/Argentina/San_Luis. Look at my update.Cade
@RobertSmith: I think your code shows that working in UTC and using astimezone works as expected. That reminds me of the advice given on the pytz doc page. "The best and simplest solution is to stick with using UTC." That link by the way, is a great read. It shows that creating an unambiguous localtime for all cases using datetime's tzinfo argument is hopeless. So option 1 is to use UTC and astimezone, option 2 is to use pytz's localize method with is_dst set to avoid ambiguity.Marquita
note: dateutil may return wrong results for any timezone that had different utc offset in the past e.g., Europe/Moscow in 2010-2015 period.Cohlier

© 2022 - 2024 — McMap. All rights reserved.