From 48bc59bca6194c4f8a40a9fcca8ceaf46f0204f9 Mon Sep 17 00:00:00 2001 From: Justin Lecher Date: Sun, 21 Dec 2014 19:31:04 +0100 Subject: [PATCH] ENH: Use datetutil.tz.gettz() instead of dateutil.zoneinfo.gettz() python-dateutil provides two implementations for gettz(), tz.gettz() and zoneinfo.gettz(). The former tries first to use system provided timezone data, where as the later always uses a bundled tarball. Upstreams recommandation for library consumers is only using tz.gettz() (1 & 2). Further more, on system which do not install the zoninfo tarball (e.g. Debian, Gentoo and Fedora) but rely on the system zoneinfo files the direct usage of zoneinfo.gettz() creates problems which result in test failures (3 - 6). For compatibility in pandas code pandas.tslib._dateutil_gettz() should be used. 1 https://github.com/dateutil/dateutil/issues/8 2 https://github.com/dateutil/dateutil/issues/11 3 https://github.com/pydata/pandas/issues/9059 4 https://github.com/pydata/pandas/issues/8639 5 https://github.com/pydata/pandas/issues/10121 6 https://github.com/pydata/pandas/issues/9663 Signed-off-by: Justin Lecher --- doc/source/whatsnew/v0.17.0.txt | 23 +++++++++++++++++++++-- pandas/compat/__init__.py | 14 ++++++++++++++ pandas/tests/test_series.py | 3 ++- pandas/tseries/tests/test_daterange.py | 2 +- pandas/tseries/tests/test_period.py | 8 ++++---- pandas/tseries/tests/test_timeseries.py | 6 +++--- pandas/tslib.pyx | 6 +++++- 7 files changed, 50 insertions(+), 12 deletions(-) diff --git a/doc/source/whatsnew/v0.17.0.txt b/doc/source/whatsnew/v0.17.0.txt index cc044bc35a707..785f35083a24e 100644 --- a/doc/source/whatsnew/v0.17.0.txt +++ b/doc/source/whatsnew/v0.17.0.txt @@ -26,6 +26,26 @@ New features Other enhancements ^^^^^^^^^^^^^^^^^^ +- Add helper functions to check for OS running on + + from pandas.compat import is_platform_windows + if is_platform_window(): + pass + +- Use dateutil.tz.gettz() after upstream recommandations + + python-dateutil provides two implementations of gettz(). + "dateutil.tz.gettz()" tries to load zone information from system provided data and fals back to + an included tarball, where as "dateutil.zoneinfo.gettz() loads directly from the tarball. Using the later on systems + which aren't providing included zone informations (e.g. Fedora or Gentoo) breaks (#9059, #8639, #9663 and #10121) + As stated by upstream in https://github.com/dateutil/dateutil/issues/11#issuecomment-70769019 only the former should be + used by library consumers. + + For compatibility in pandas following code should be used + + from pandas.tslib import _dateutil_gettz as gettz + tz = gettz('Europe/Brussels') + .. _whatsnew_0170.api: Backwards incompatible API changes @@ -66,5 +86,4 @@ Bug Fixes - Bug in ``Timestamp``'s' ``microsecond``, ``quarter``, ``dayofyear``, ``week`` and ``daysinmonth`` properties return ``np.int`` type, not built-in ``int``. (:issue:`10050`) - Bug in ``NaT`` raises ``AttributeError`` when accessing to ``daysinmonth``, ``dayofweek`` properties. (:issue:`10096`) - - +- Bug in dateutil.tz.gettz() vs. dateutil.zoneinfo.gettz() usage which creates problems on systems solely rely on systems timezone data (:issue:`9123`, :issue:`9059`, :issue:`8639`, :issue:`9663`, :issue:`10121`) diff --git a/pandas/compat/__init__.py b/pandas/compat/__init__.py index 6be0facf2bffc..2a273629544cb 100644 --- a/pandas/compat/__init__.py +++ b/pandas/compat/__init__.py @@ -26,6 +26,7 @@ Other items: * OrderedDefaultDict +* platform checker """ # pylint disable=W0611 import functools @@ -754,3 +755,16 @@ def __missing__(self, key): def __reduce__(self): # optional, for pickle support args = self.default_factory if self.default_factory else tuple() return type(self), args, None, None, list(self.items()) + + +# https://github.com/pydata/pandas/pull/9123 +def is_platform_windows(): + return sys.platform == 'win32' or sys.platform == 'cygwin' + + +def is_platform_linux(): + return sys.platform == 'linux2' + + +def is_platform_mac(): + return sys.platform == 'darwin' diff --git a/pandas/tests/test_series.py b/pandas/tests/test_series.py index 22f8aee1e0a4e..925cfa875196c 100644 --- a/pandas/tests/test_series.py +++ b/pandas/tests/test_series.py @@ -5398,7 +5398,8 @@ def test_getitem_setitem_datetime_tz_pytz(self): def test_getitem_setitem_datetime_tz_dateutil(self): tm._skip_if_no_dateutil(); from dateutil.tz import tzutc - from dateutil.zoneinfo import gettz + from pandas.tslib import _dateutil_gettz as gettz + tz = lambda x: tzutc() if x == 'UTC' else gettz(x) # handle special case for utc in dateutil from pandas import date_range diff --git a/pandas/tseries/tests/test_daterange.py b/pandas/tseries/tests/test_daterange.py index 841d81c15b4e9..69b1d84670d45 100644 --- a/pandas/tseries/tests/test_daterange.py +++ b/pandas/tseries/tests/test_daterange.py @@ -441,7 +441,7 @@ def test_month_range_union_tz_pytz(self): def test_month_range_union_tz_dateutil(self): _skip_if_windows_python_3() tm._skip_if_no_dateutil() - from dateutil.zoneinfo import gettz as timezone + from pandas.tslib import _dateutil_gettz as timezone tz = timezone('US/Eastern') early_start = datetime(2011, 1, 1) diff --git a/pandas/tseries/tests/test_period.py b/pandas/tseries/tests/test_period.py index 70c706fc66398..0218af63ca7d6 100644 --- a/pandas/tseries/tests/test_period.py +++ b/pandas/tseries/tests/test_period.py @@ -101,15 +101,15 @@ def test_timestamp_tz_arg(self): pytz.timezone('Europe/Brussels').normalize(p).tzinfo) def test_timestamp_tz_arg_dateutil(self): - import dateutil + from pandas.tslib import _dateutil_gettz as gettz from pandas.tslib import maybe_get_tz p = Period('1/1/2005', freq='M').to_timestamp(tz=maybe_get_tz('dateutil/Europe/Brussels')) - self.assertEqual(p.tz, dateutil.zoneinfo.gettz('Europe/Brussels')) + self.assertEqual(p.tz, gettz('Europe/Brussels')) def test_timestamp_tz_arg_dateutil_from_string(self): - import dateutil + from pandas.tslib import _dateutil_gettz as gettz p = Period('1/1/2005', freq='M').to_timestamp(tz='dateutil/Europe/Brussels') - self.assertEqual(p.tz, dateutil.zoneinfo.gettz('Europe/Brussels')) + self.assertEqual(p.tz, gettz('Europe/Brussels')) def test_timestamp_nat_tz(self): t = Period('NaT', freq='M').to_timestamp() diff --git a/pandas/tseries/tests/test_timeseries.py b/pandas/tseries/tests/test_timeseries.py index 0c4961d80a5f4..6c20b02324688 100644 --- a/pandas/tseries/tests/test_timeseries.py +++ b/pandas/tseries/tests/test_timeseries.py @@ -417,9 +417,9 @@ def test_timestamp_to_datetime_explicit_pytz(self): def test_timestamp_to_datetime_explicit_dateutil(self): _skip_if_windows_python_3() tm._skip_if_no_dateutil() - import dateutil + from pandas.tslib import _dateutil_gettz as gettz rng = date_range('20090415', '20090519', - tz=dateutil.zoneinfo.gettz('US/Eastern')) + tz=gettz('US/Eastern')) stamp = rng[0] dtval = stamp.to_pydatetime() @@ -1807,7 +1807,7 @@ def test_append_concat_tz_explicit_pytz(self): def test_append_concat_tz_dateutil(self): # GH 2938 tm._skip_if_no_dateutil() - from dateutil.zoneinfo import gettz as timezone + from pandas.tslib import _dateutil_gettz as timezone rng = date_range('5/8/2012 1:45', periods=10, freq='5T', tz='dateutil/US/Eastern') diff --git a/pandas/tslib.pyx b/pandas/tslib.pyx index 66f14bfb0346a..2b45718d1f9ea 100644 --- a/pandas/tslib.pyx +++ b/pandas/tslib.pyx @@ -41,7 +41,11 @@ from datetime import time as datetime_time # dateutil compat from dateutil.tz import (tzoffset, tzlocal as _dateutil_tzlocal, tzfile as _dateutil_tzfile, tzutc as _dateutil_tzutc) -from dateutil.zoneinfo import gettz as _dateutil_gettz +from pandas.compat import is_platform_windows +if is_platform_windows(): + from dateutil.zoneinfo import gettz as _dateutil_gettz +else: + from dateutil.tz import gettz as _dateutil_gettz from pytz.tzinfo import BaseTzInfo as _pytz_BaseTzInfo from pandas.compat import parse_date, string_types, PY3, iteritems