diff --git a/.flake8 b/.flake8 index 0c9fcd3fc117..4c64cd4eada0 100644 --- a/.flake8 +++ b/.flake8 @@ -18,6 +18,8 @@ exclude = versioneer.py tools/gh_api.py tools/github_stats.py + .tox + .eggs per-file-ignores = setup.py: E402 diff --git a/.travis.yml b/.travis.yml index bb928ebd2599..b586846373bd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -158,14 +158,18 @@ before_script: | fi script: + # each script we want to run need to go in it's own section and the program you want + # to fail travis need to be the last thing called - | echo "Calling pytest with the following arguments: $PYTEST_ADDOPTS" python -mpytest + - tox -e pytz - | if [[ $RUN_FLAKE8 == 1 ]]; then flake8 --statistics && echo "Flake8 passed without any issues!" fi + before_cache: | rm -rf $HOME/.cache/matplotlib/tex.cache rm -rf $HOME/.cache/matplotlib/test_cache diff --git a/INSTALL.rst b/INSTALL.rst index d83228dfafd1..2df97b6f7d7d 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -144,7 +144,6 @@ Matplotlib requires the following dependencies: * `dateutil `_ (>= 2.1) * `kiwisolver `_ (>= 1.0.0) * `pyparsing `_ -* `pytz `_ Optionally, you can also install a number of packages to enable better user interface toolkits. See :ref:`what-is-a-backend` for more details on the diff --git a/build_alllocal.cmd b/build_alllocal.cmd index 56e2968a195f..54bc69432fb7 100644 --- a/build_alllocal.cmd +++ b/build_alllocal.cmd @@ -1,6 +1,6 @@ :: This assumes you have installed all the dependencies via conda packages: :: # create a new environment with the required packages -:: conda create -n "matplotlib_build" python=3.5 numpy python-dateutil pyparsing pytz tornado cycler tk libpng zlib freetype +:: conda create -n "matplotlib_build" python=3.5 numpy python-dateutil pyparsing tornado cycler tk libpng zlib freetype :: activate matplotlib_build :: if you want qt backend, you also have to install pyqt :: conda install pyqt diff --git a/doc/api/next_api_changes/2018-06-01-PG-pytz-ectomy.rst b/doc/api/next_api_changes/2018-06-01-PG-pytz-ectomy.rst new file mode 100644 index 000000000000..ebaa2f620648 --- /dev/null +++ b/doc/api/next_api_changes/2018-06-01-PG-pytz-ectomy.rst @@ -0,0 +1,4 @@ +Removed ``pytz`` as a dependency +-------------------------------- + +Since ``dateutil`` and ``pytz`` both provide time zones, and matplotlib already depends on ``dateutil``, matplotlib will now use ``dateutil`` time zones internally and drop the redundant dependency on ``pytz``. While ``dateutil`` time zones are preferred (and currently recommended in the Python documentation), the explicit use of ``pytz`` zones is still supported. diff --git a/doc/glossary/index.rst b/doc/glossary/index.rst index 544e78b95acd..b951faef48e1 100644 --- a/doc/glossary/index.rst +++ b/doc/glossary/index.rst @@ -90,12 +90,6 @@ Glossary language widely used for scripting, application development, web application servers, scientific computing and more. - pytz - `pytz `_ provides the Olson tz - database in Python. it allows accurate and cross platform - timezone calculations and solves the issue of ambiguous times at - the end of daylight savings - Qt `Qt `__ is a cross-platform application framework for desktop and embedded development. diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index ecdeed49ebd8..54e697a2e481 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -1747,8 +1747,8 @@ def axis_date(self, tz=None): # the registered converter can be selected, and the "units" attribute, # which is the timezone, can be set. if isinstance(tz, str): - import pytz - tz = pytz.timezone(tz) + import dateutil.tz + tz = dateutil.tz.gettz(tz) self.update_units(datetime.datetime(2009, 1, 1, 0, 0, 0, 0, tz)) def get_tick_space(self): diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index 71177a9eaaa0..80d790ded87c 100644 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -1,7 +1,6 @@ """ Matplotlib provides sophisticated date plotting capabilities, standing on the -shoulders of python :mod:`datetime`, the add-on modules :mod:`pytz` and -:mod:`dateutil`. +shoulders of python :mod:`datetime` and the add-on module :mod:`dateutil`. .. _date-format: @@ -46,11 +45,9 @@ All the Matplotlib date converters, tickers and formatters are timezone aware. If no explicit timezone is provided, the rcParam ``timezone`` is assumend. If -you want to use a custom time zone, pass a :class:`pytz.timezone` instance +you want to use a custom time zone, pass a :class:`datetime.tzinfo` instance with the tz keyword argument to :func:`num2date`, :func:`.plot_date`, and any custom date tickers or locators you create. -See `pytz `_ for information on :mod:`pytz` and -timezone handling. A wide range of specific and general purpose date tick locators and formatters are provided in this module. See @@ -58,7 +55,7 @@ and formatters. These are described below. -The `dateutil module `_ provides +The `dateutil module `_ provides additional code to handle date ticking, making it easy to place ticks on any kinds of dates. See examples below. @@ -110,7 +107,7 @@ :class:`matplotlib.dates.rrulewrapper`. The :class:`rrulewrapper` is a simple wrapper around a :class:`dateutil.rrule` (`dateutil - `_) which allow almost + `_) which allow almost arbitrary date tick specifications. See `rrule example <../gallery/ticks_and_spines/date_demo_rrule.html>`_. @@ -149,6 +146,7 @@ SECONDLY) from dateutil.relativedelta import relativedelta import dateutil.parser +import dateutil.tz import numpy as np import matplotlib @@ -175,23 +173,7 @@ _log = logging.getLogger(__name__) -# Make a simple UTC instance so we don't always have to import -# pytz. From the python datetime library docs: - -class _UTC(datetime.tzinfo): - """UTC""" - - def utcoffset(self, dt): - return datetime.timedelta(0) - - def tzname(self, dt): - return "UTC" - - def dst(self, dt): - return datetime.timedelta(0) - - -UTC = _UTC() +UTC = datetime.timezone.utc def _get_rc_timezone(): @@ -201,8 +183,7 @@ def _get_rc_timezone(): s = matplotlib.rcParams['timezone'] if s == 'UTC': return UTC - import pytz - return pytz.timezone(s) + return dateutil.tz.gettz(s) """ diff --git a/lib/matplotlib/sphinxext/tests/test_tinypages.py b/lib/matplotlib/sphinxext/tests/test_tinypages.py index 9ec300894760..7a149059ce10 100644 --- a/lib/matplotlib/sphinxext/tests/test_tinypages.py +++ b/lib/matplotlib/sphinxext/tests/test_tinypages.py @@ -16,6 +16,7 @@ def test_tinypages(tmpdir): + pytest.importorskip('sphinx') html_dir = pjoin(str(tmpdir), 'html') doctree_dir = pjoin(str(tmpdir), 'doctrees') # Build the pages with warnings turned into errors diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 093c89d7c0b9..ed28e1c6a2a2 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4,7 +4,7 @@ import datetime -import pytz +import dateutil.tz as dutz import numpy as np from numpy import ma @@ -5331,8 +5331,9 @@ def test_bar_uint8(): @image_comparison(baseline_images=['date_timezone_x'], extensions=['png']) def test_date_timezone_x(): # Tests issue 5575 - time_index = [pytz.timezone('Canada/Eastern').localize(datetime.datetime( - year=2016, month=2, day=22, hour=x)) for x in range(3)] + time_index = [datetime.datetime(2016, 2, 22, hour=x, + tzinfo=dutz.gettz('Canada/Eastern')) + for x in range(3)] # Same Timezone fig = plt.figure(figsize=(20, 12)) @@ -5348,8 +5349,9 @@ def test_date_timezone_x(): extensions=['png']) def test_date_timezone_y(): # Tests issue 5575 - time_index = [pytz.timezone('Canada/Eastern').localize(datetime.datetime( - year=2016, month=2, day=22, hour=x)) for x in range(3)] + time_index = [datetime.datetime(2016, 2, 22, hour=x, + tzinfo=dutz.gettz('Canada/Eastern')) + for x in range(3)] # Same Timezone fig = plt.figure(figsize=(20, 12)) @@ -5366,8 +5368,9 @@ def test_date_timezone_y(): extensions=['png']) def test_date_timezone_x_and_y(): # Tests issue 5575 - time_index = [pytz.timezone('UTC').localize(datetime.datetime( - year=2016, month=2, day=22, hour=x)) for x in range(3)] + UTC = datetime.timezone.utc + time_index = [datetime.datetime(2016, 2, 22, hour=x, tzinfo=UTC) + for x in range(3)] # Same Timezone fig = plt.figure(figsize=(20, 12)) diff --git a/lib/matplotlib/tests/test_dates.py b/lib/matplotlib/tests/test_dates.py index 67b902f121a1..db153be5ff97 100644 --- a/lib/matplotlib/tests/test_dates.py +++ b/lib/matplotlib/tests/test_dates.py @@ -2,10 +2,10 @@ import tempfile from unittest.mock import Mock -import dateutil +import dateutil.tz +import dateutil.rrule import numpy as np import pytest -import pytz from matplotlib.testing.decorators import image_comparison import matplotlib.pyplot as plt @@ -13,6 +13,14 @@ import matplotlib.dates as mdates +def __has_pytz(): + try: + import pytz + return True + except ImportError: + return False + + def test_date_numpyx(): # test that numpy dates work properly... base = datetime.datetime(2017, 1, 1) @@ -180,8 +188,8 @@ def test_RRuleLocator(): def test_RRuleLocator_dayrange(): loc = mdates.DayLocator() - x1 = datetime.datetime(year=1, month=1, day=1, tzinfo=pytz.UTC) - y1 = datetime.datetime(year=1, month=1, day=16, tzinfo=pytz.UTC) + x1 = datetime.datetime(year=1, month=1, day=1, tzinfo=mdates.UTC) + y1 = datetime.datetime(year=1, month=1, day=16, tzinfo=mdates.UTC) loc.tick_values(x1, y1) # On success, no overflow error shall be thrown @@ -482,8 +490,8 @@ def test_date_inverted_limit(): def _test_date2num_dst(date_range, tz_convert): # Timezones - BRUSSELS = pytz.timezone('Europe/Brussels') - UTC = pytz.UTC + BRUSSELS = dateutil.tz.gettz('Europe/Brussels') + UTC = mdates.UTC # Create a list of timezone-aware datetime objects in UTC # Interval is 0b0.0000011 days, to prevent float rounding issues @@ -575,10 +583,7 @@ def tz_convert(*args): _test_date2num_dst(pd.date_range, tz_convert) -@pytest.mark.parametrize("attach_tz, get_tz", [ - (lambda dt, zi: zi.localize(dt), lambda n: pytz.timezone(n)), - (lambda dt, zi: dt.replace(tzinfo=zi), lambda n: dateutil.tz.gettz(n))]) -def test_rrulewrapper(attach_tz, get_tz): +def _test_rrulewrapper(attach_tz, get_tz): SYD = get_tz('Australia/Sydney') dtstart = attach_tz(datetime.datetime(2017, 4, 1, 0), SYD) @@ -593,6 +598,25 @@ def test_rrulewrapper(attach_tz, get_tz): assert act == exp +def test_rrulewrapper(): + def attach_tz(dt, zi): + return dt.replace(tzinfo=zi) + + _test_rrulewrapper(attach_tz, dateutil.tz.gettz) + + +@pytest.mark.pytz +@pytest.mark.skipif(not __has_pytz(), reason="Requires pytz") +def test_rrulewrapper_pytz(): + # Test to make sure pytz zones are supported in rrules + import pytz + + def attach_tz(dt, zi): + return zi.localize(dt) + + _test_rrulewrapper(attach_tz, pytz.timezone) + + def test_DayLocator(): with pytest.raises(ValueError): mdates.DayLocator(interval=-1) diff --git a/requirements/testing/travis_all.txt b/requirements/testing/travis_all.txt index aff3cab60ec6..364e43fd8875 100644 --- a/requirements/testing/travis_all.txt +++ b/requirements/testing/travis_all.txt @@ -15,5 +15,5 @@ pytest-rerunfailures pytest-timeout pytest-xdist python-dateutil -sphinx tornado +tox diff --git a/setupext.py b/setupext.py index ce1d807c9c60..03abbc77636f 100644 --- a/setupext.py +++ b/setupext.py @@ -1341,7 +1341,6 @@ def get_install_requires(self): "kiwisolver>=1.0.1", "pyparsing>=2.0.1,!=2.0.4,!=2.1.2,!=2.1.6", "python-dateutil>=2.1", - "pytz", ] diff --git a/tox.ini b/tox.ini index 2193faf4af15..f48969ecc28b 100644 --- a/tox.ini +++ b/tox.ini @@ -18,3 +18,11 @@ commands = pytest --pyargs matplotlib deps = pytest + +[testenv:pytz] +changedir = /tmp +commands = + pytest -m pytz {toxinidir} +deps = + pytest + pytz