From 5129b0b836fe1e7fd38047f540f54f85046ade14 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Fri, 1 Jun 2018 09:15:10 -0400 Subject: [PATCH 01/10] Remove pytz dependency from axis.py --- lib/matplotlib/axis.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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): From eec4a5ab9e533947316bb7ed4158af16ac4df5b9 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Fri, 1 Jun 2018 09:16:07 -0400 Subject: [PATCH 02/10] Remove pytz from dates.py --- lib/matplotlib/dates.py | 33 +++++++-------------------------- 1 file changed, 7 insertions(+), 26 deletions(-) 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) """ From d6339b5eee3b9be9367358dc256633aaee07f185 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Fri, 1 Jun 2018 09:39:16 -0400 Subject: [PATCH 03/10] TST: Remove pytz as hard dependency in tests --- lib/matplotlib/tests/test_axes.py | 17 +++++++----- lib/matplotlib/tests/test_dates.py | 44 +++++++++++++++++++++++------- 2 files changed, 44 insertions(+), 17 deletions(-) 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) From 23b41b15f6eb659b4b550baf96eb9aa65a57dda4 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Fri, 1 Jun 2018 09:39:45 -0400 Subject: [PATCH 04/10] Remove pytz from build dependencies --- setupext.py | 1 - 1 file changed, 1 deletion(-) 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", ] From 75c5c48b5d8195fd38285e06a693719c63e6460c Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Fri, 1 Jun 2018 09:39:56 -0400 Subject: [PATCH 05/10] DOC: Remove pytz from documentation --- INSTALL.rst | 1 - build_alllocal.cmd | 2 +- doc/glossary/index.rst | 6 ------ 3 files changed, 1 insertion(+), 8 deletions(-) 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/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. From f8e76bff09131b1f1f327efb6f2f89b0d59a2e92 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Fri, 1 Jun 2018 10:05:02 -0400 Subject: [PATCH 06/10] DOC: Add API changes entry for pytz removal --- doc/api/next_api_changes/2018-06-01-PG-pytz-ectomy.rst | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 doc/api/next_api_changes/2018-06-01-PG-pytz-ectomy.rst 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. From 1364784212db062c67864b8ca0bd149bf13c2912 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Fri, 1 Jun 2018 10:05:24 -0400 Subject: [PATCH 07/10] CI: Add separate test environment for pytz The separate tests with and without pytz are done to try to minimize the possibility of implicit dependencies on pytz. Run the second test in it's own entry in the script section of the travis file so that it does not mask failures from other sections. --- .travis.yml | 4 ++++ requirements/testing/travis_all.txt | 1 + tox.ini | 8 ++++++++ 3 files changed, 13 insertions(+) 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/requirements/testing/travis_all.txt b/requirements/testing/travis_all.txt index aff3cab60ec6..8f1eaab21a40 100644 --- a/requirements/testing/travis_all.txt +++ b/requirements/testing/travis_all.txt @@ -17,3 +17,4 @@ pytest-xdist python-dateutil sphinx tornado +tox 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 From ab92163d6a8077bc22951562886099cf0e48469c Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 5 Jul 2018 22:15:47 -0400 Subject: [PATCH 08/10] CI: only install sphinx on py35 build Sphinx pulls in pytz which we want to avoid to test running without it. The sphinx related code is well tested by the circle CI that builds the docs. --- requirements/testing/travis_all.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements/testing/travis_all.txt b/requirements/testing/travis_all.txt index 8f1eaab21a40..364e43fd8875 100644 --- a/requirements/testing/travis_all.txt +++ b/requirements/testing/travis_all.txt @@ -15,6 +15,5 @@ pytest-rerunfailures pytest-timeout pytest-xdist python-dateutil -sphinx tornado tox From 07fdbe828000514e81081e2ff9d2c664922afe02 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 6 Jul 2018 09:08:50 -0400 Subject: [PATCH 09/10] TST: skip sphinx tests if no sphinx installed --- lib/matplotlib/sphinxext/tests/test_tinypages.py | 1 + 1 file changed, 1 insertion(+) 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 From 590ee8e133903accb519c2618c05bd93f39a4557 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sun, 8 Jul 2018 22:44:44 -0400 Subject: [PATCH 10/10] TST: exclude tox files from flake8 checking --- .flake8 | 2 ++ 1 file changed, 2 insertions(+) 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