Repository: SciTools/nc-time-axis

Issue:
Deprecate `CalendarDateTime`?
`CalendarDateTime` served a purpose in the early days of nc-time-axis, but I now think it may not be needed anymore, particularly once #75 is addressed (#80).  `cftime.datetime` instances now have a meaningful `calendar` attribute and their comparisons now check that calendars and datetime components are equal.  Should we include a `DeprecationWarning` in the next release of nc-time-axis?

As an aside, reading some of the existing tests for `CalendarDateTime` makes me a little uneasy now that `cftime.datetime` objects have a default calendar of `"gregorian"` instead of `None`:
https://github.com/SciTools/nc-time-axis/blob/59c567d307eb8ced44f2c95b393c93ba571e486c/nc_time_axis/tests/unit/test_NetCDFTimeConverter.py#L116-L121
Apparently these still pass despite the fact that the `calendar` of the datetime object does not match the calendar of the `CalendarDateTime` object.

Discussion:
codecov-commenter: # [Codecov](https://codecov.io/gh/SciTools/nc-time-axis/pull/86?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=SciTools) Report
> Merging [#86](https://codecov.io/gh/SciTools/nc-time-axis/pull/86?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=SciTools) (8c574f9) into [main](https://codecov.io/gh/SciTools/nc-time-axis/commit/837fec3379b60ea89d2a8fa34da20de13e22fd48?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=SciTools) (837fec3) will **increase** coverage by `0.02%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/SciTools/nc-time-axis/pull/86/graphs/tree.svg?width=650&height=150&src=pr&token=JicwCCHwLd&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=SciTools)](https://codecov.io/gh/SciTools/nc-time-axis/pull/86?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=SciTools)

```diff
@@            Coverage Diff             @@
##             main      #86      +/-   ##
==========================================
+ Coverage   94.30%   94.32%   +0.02%     
==========================================
  Files           1        1              
  Lines         193      194       +1     
  Branches       44       44              
==========================================
+ Hits          182      183       +1     
  Misses          6        6              
  Partials        5        5              
```


| [Impacted Files](https://codecov.io/gh/SciTools/nc-time-axis/pull/86?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=SciTools) | Coverage Δ | |
|---|---|---|
| [nc\_time\_axis/\_\_init\_\_.py](https://codecov.io/gh/SciTools/nc-time-axis/pull/86/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=SciTools#diff-bmNfdGltZV9heGlzL19faW5pdF9fLnB5) | `94.32% <100.00%> (+0.02%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/SciTools/nc-time-axis/pull/86?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=SciTools).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=SciTools)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/SciTools/nc-time-axis/pull/86?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=SciTools). Last update [837fec3...8c574f9](https://codecov.io/gh/SciTools/nc-time-axis/pull/86?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=SciTools). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=SciTools).


Patch:
diff --git a/.flake8 b/.flake8
index d8ba528..9e60cc1 100644
--- a/.flake8
+++ b/.flake8
@@ -26,3 +26,4 @@ ignore =
 exclude = 
     .eggs
     build
+    docs/conf.py
diff --git a/.readthedocs.yml b/.readthedocs.yml
new file mode 100644
index 0000000..5fa95cf
--- /dev/null
+++ b/.readthedocs.yml
@@ -0,0 +1,16 @@
+version: 2
+
+build:
+    image: latest
+
+conda:
+    environment: requirements/docs.yml
+
+sphinx:
+    configuration: docs/conf.py
+    fail_on_warning: false
+
+python:
+    install:
+        - method: setuptools
+          path: .
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000..d4bb2cb
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS    ?=
+SPHINXBUILD   ?= sphinx-build
+SOURCEDIR     = .
+BUILDDIR      = _build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/api.rst b/docs/api.rst
new file mode 100644
index 0000000..6211291
--- /dev/null
+++ b/docs/api.rst
@@ -0,0 +1,61 @@
+.. _api:
+
+.. currentmodule:: nc_time_axis
+
+API reference
+=============
+
+This page provides a reference for the API of nc-time-axis.
+
+CalendarDateTime
+----------------
+
+For historical reasons, nc-time-axis defines a :py:class:`CalendarDateTime`
+class which encapsulates a :py:class:`cftime.datetime` object and its calendar
+type.  This is no longer needed, and will be removed in nc-time-axis version
+1.5, but we document it here for now.
+
+.. autosummary::
+    :toctree: _api_generated/
+
+    CalendarDateTime
+
+Formatters
+----------
+
+The :py:class:`AutoCFTimeFormatter` is what is used by default when plotting
+:py:class:`cftime.datetime` axis; it will automatically pick a date label format
+depending on the axis range.  If you would like more control over the format of
+the date labels you may use the :py:class:`CFTimeFormatter` class.
+
+.. autosummary::
+    :toctree: _api_generated/
+
+    AutoCFTimeFormatter
+    CFTimeFormatter
+
+Locators
+--------
+
+The :py:class:`NetCDFTimeDateLocator` is what is used by default to set the tick
+locations depending on the range of the axis.  For finer-grained control over
+the tick locations, use matplotlib's :py:meth:`matplotlib.axes.Axes.set_xticks`
+or :py:meth:`matplotlib.axes.Axes.set_yticks` methods to set the tick locations
+explicitly.
+
+.. autosummary::
+    :toctree: _api_generated/
+
+    NetCDFTimeDateLocator
+
+Converters
+----------
+
+Internally, nc-time-axis must convert times to numerical values so that
+matplotlib can plot them on axes.  The :py:class:`NetCDFTimeConverter` is used
+for this purpose.
+
+.. autosummary::
+    :toctree: _api_generated/
+
+    NetCDFTimeConverter
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 0000000..8ab5a7c
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,79 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# This file only contains a selection of the most common options. For a full
+# list see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+# -- Path setup --------------------------------------------------------------
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+# import os
+# import sys
+# sys.path.insert(0, os.path.abspath('.'))
+import datetime
+
+# -- Project information -----------------------------------------------------
+
+project = 'nc-time-axis'
+year = datetime.datetime.today().year
+copyright = f'2016-{year}, nc-time-axis developers'
+author = 'nc-time-axis developers'
+
+
+# -- General configuration ---------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+    'sphinx.ext.autodoc',
+    'sphinx.ext.autosummary',
+    "sphinx.ext.extlinks",
+    "sphinx.ext.intersphinx",
+    'numpydoc',
+    "IPython.sphinxext.ipython_directive",
+    "IPython.sphinxext.ipython_console_highlighting",
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+#
+html_theme = 'sphinx_rtd_theme'
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+intersphinx_mapping = {
+    "cftime": ("https://unidata.github.io/cftime", None),
+    "matplotlib": ("https://matplotlib.org/stable", None),
+    "numpy": ("https://numpy.org/doc/stable", None),
+    "python": ("https://docs.python.org/3/", None),
+}
+
+extlinks = {
+    "discussion": ("https://github.com/SciTools/nc-time-axis/discussions/%s", "GH"),
+    "issue": ("https://github.com/SciTools/nc-time-axis/issues/%s", "GH"),
+    "pull": ("https://github.com/SciTools/nc-time-axis/pull/%s", "PR"),
+}
+
+autosummary_generate = True
+
+html_theme_options = {
+    'navigation_depth': 2,
+}
diff --git a/docs/examples.rst b/docs/examples.rst
new file mode 100644
index 0000000..4184541
--- /dev/null
+++ b/docs/examples.rst
@@ -0,0 +1,56 @@
+.. _examples:
+
+.. currentmodule:: nc_time_axis
+
+Examples
+========
+
+Basic usage
+-----------
+
+nc-time-axis works by adding converters to matplotlib's flexible `units registry
+<https://matplotlib.org/stable/api/units_api.html>`_.  To register its
+converters, simply import nc-time-axis.  Then you will be able to make plots
+with :py:class:`cftime.datetime` axes.
+
+.. ipython:: python
+    :okwarning:
+
+    import cftime
+    import matplotlib.pyplot as plt
+    import nc_time_axis
+    import numpy as np
+
+    fig, ax = plt.subplots(1, 1)
+    x = np.linspace(0, 6 * np.pi)
+    y = 0.5 * x + np.sin(x)
+    times = cftime.num2date(x, units="days since 2000-01-01", calendar="noleap")
+    ax.plot(times, y);
+
+    @savefig basic.png
+    fig.show()
+
+Setting the axes ticks and tick format
+--------------------------------------
+
+In the first example, the ticks and tick label formats were chosen automatically
+using heuristics in nc-time-axis.  If you would like to explicitly set the tick
+positions and label format you may do so using
+:py:meth:`matplotlib.axes.Axes.set_xticks` and :py:class:`CFTimeFormatter`.  The
+:py:class:`CFTimeFormatter` takes in a date format string (see `the Python
+documentation
+<https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes>`_
+for acceptable format codes) and the calendar type of the axis (see
+the :py:class:`cftime.datetime` documentation for valid calendar strings).
+
+.. ipython:: python
+    :okwarning:
+
+    fig, ax = plt.subplots(1, 1)
+    ax.plot(times, y);
+    ax.set_xticks([cftime.datetime(2000, 1, day, calendar="noleap") for day in range(2, 19, 4)]);
+    formatter = nc_time_axis.CFTimeFormatter("%m-%d %H:%M", "noleap")
+    ax.xaxis.set_major_formatter(formatter)
+
+    @savefig set_ticks.png
+    fig.show()
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 0000000..7d892ff
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,24 @@
+.. nc-time-axis documentation master file, created by
+   sphinx-quickstart on Tue Aug 17 14:53:26 2021.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+nc-time-axis
+============
+
+**nc-time-axis** is a package that enables making plots in matplotlib with axes made
+up of :py:class:`cftime.datetime` dates with any calendar type.  
+
+.. toctree::
+   :caption: Getting started
+   :maxdepth: 1
+
+   installation
+   examples
+
+.. toctree::
+   :caption: Reference
+   :maxdepth: 1
+
+   release-notes
+   api
diff --git a/docs/installation.rst b/docs/installation.rst
new file mode 100644
index 0000000..aae31c5
--- /dev/null
+++ b/docs/installation.rst
@@ -0,0 +1,30 @@
+.. _installation:
+
+Installation
+============
+
+Minimum requirements
+--------------------
+
+* `cftime <https://unidata.github.io/cftime>`_ >= 1.5
+* `matplotlib <https://matplotlib.org/stable>`_
+* `numpy <https://numpy.org/doc/stable>`_
+* `python <https://docs.python.org/3>`_ >= 3.7
+
+Test requirements
+-----------------
+
+For testing, nc-time-axis also depends on pytest:
+
+* `pytest <https://docs.pytest.org/en/6.2.x>`_ >= 6.0
+
+Installation methods
+--------------------
+
+nc-time-axis can be installed through `conda <https://docs.conda.io/projects/conda/en/latest/index.html>`_::
+
+    conda install -c conda-forge nc-time-axis
+
+or through `pip <https://pip.pypa.io/en/stable>`_::
+
+    pip install nc-time-axis
diff --git a/docs/release-notes.rst b/docs/release-notes.rst
new file mode 100644
index 0000000..e51b25d
--- /dev/null
+++ b/docs/release-notes.rst
@@ -0,0 +1,207 @@
+.. _release_notes:
+
+.. currentmodule:: nc_time_axis
+
+Release notes
+=============
+
+v1.4.0 (unreleased)
+-------------------
+
+New features
+~~~~~~~~~~~~
+
+* Added a :py:class:`CFTimeFormatter` class to enable custom formatting for
+  :py:class:`cftime.datetime` ticks (:issue:`41`, :pull:`84`).  By `Spencer
+  Clark <https://github.com/spencerkclark>`_.
+* Added ability to plot calendar-aware :py:class:`cftime.datetime` objects, e.g.
+  ``cftime.datetime(2000, 1, 1, calendar="noleap")``, available as of cftime
+  version 1.3.0 (:issue:`75`, :pull:`80`).  By `Spencer Clark
+  <https://github.com/spencerkclark>`_.
+
+Bug fixes
+~~~~~~~~~
+
+* Enabled :py:meth:`NetCDFTimeConverter.convert` to take a :py:class:`list` of
+  datetimes as an argument, allowing matplotlib methods like
+  :py:meth:`matplotlib.axes.Axes.axvspan` and
+  :py:meth:`matplotlib.axes.Axes.fill_between` to work properly with cftime
+  values (:issue:`47`, :issue:`74`, :pull:`78`).  By `Pascal Bourgault
+  <https://github.com/aulemahal>`_.
+* Fixed a bug that resulted in the resolution of tick labels being inconsistent 
+  with the resolution of tick values (:issue:`48`, :pull:`79`).  By `Spencer
+  Clark <https://github.com/spencerkclark>`_.
+* Fixed a bug that prevented users from being able to explicitly set the ticks 
+  along axes using :py:meth:`matplotlib.axes.Axes.set_xticks` or
+  :py:meth:`matplotlib.axes.Axes.set_yticks` (:issue:`41`, :pull:`84`).  By
+  `Spencer Clark <https://github.com/spencerkclark>`_.
+
+Documentation
+~~~~~~~~~~~~~
+
+* Added a start on Read the Docs documentation.  Added examples, release notes,
+  and NumPy-style docstrings to nc-time-axis classes (:issue:`62`, :pull:`87`).
+  By `Spencer Clark <https://github.com/spencerkclark>`_.
+
+
+v1.3.1 (June 14th, 2021)
+------------------------
+
+Requirements
+~~~~~~~~~~~~
+
+* nc-time-axis now requires cftime of at least version 1.5 (:discussion:`61`,
+  :pull:`69`).  By `Bill Little <https://github.com/bjlittle>`_.
+
+
+v1.3.0 (June 11th, 2021)
+------------------------
+
+Requirements
+~~~~~~~~~~~~
+
+* Support for Python 2 was dropped.
+
+Infrastructure
+~~~~~~~~~~~~~~
+
+* Migrated continuous integration from Travis CI to Cirrus CI through GitHub
+  Actions.  Added formatting and linting checks.  Updated packaging to use
+  `setuptools <https://setuptools.readthedocs.io/en/latest/>`_.  Added issue and
+  pull request templates.  Enabled GitHub discussions.  For more details see
+  :issue:`63` and :pull:`66`.  By `Bill Little <https://github.com/bjlittle>`_.
+* Changed the name of the primary branch from "master" to "main" (:pull:`68`).
+  By `Bill Little <https://github.com/bjlittle>`_.
+
+Bug fixes
+~~~~~~~~~
+
+* Removed ticks with year zero in calendars without year zero.  Previously these
+  would lead to errors in cftime, which would prevent plotting (:issue:`44`,
+  :pull:`50`).  By `Julius Busecke <https://github.com/jbusecke>`_.
+* Removed internal use of the deprecated :py:class:`cftime.utime` class
+  (:pull:`59` through :pull:`66`).  By `Pascal Bourgault
+  <https://github.com/aulemahal>`_.
+* Removed internal use of the deprecated :py:class:`numpy.object` data type
+  (:pull:`56` through :pull:`66`).  By `Mathias Hauser
+  <https://github.com/mathause>`_.
+* Updated internals for compatibility with the now calendar-aware
+  :py:class:`cftime.datetime` object (:pull:`51` through :pull:`66`).  By `Jeff
+  Whitaker <https://github.com/jswhit>`_.
+* Improved handling of scalar values passed to
+  :py:meth:`NetCDFTimeConverter.convert` (:issue:`45`, :pull:`46`).  By `Spencer
+  Clark <https://github.com/spencerkclark>`_.
+
+
+v1.2.0 (January 25th, 2019)
+---------------------------
+
+Infrastructure
+~~~~~~~~~~~~~~
+
+* Included license in package data (:pull:`37`).  By `Filipe Fernandes
+  <https://github.com/ocefpaf>`_.
+
+New features
+~~~~~~~~~~~~
+
+* Added ability to directly plot subclasses of :py:class:`cftime.datetime`, e.g.
+  :py:class:`cftime.DatetimeNoLeap`, instead of requiring
+  :py:class:`CalendarDateTime` objects (:pull:`42`).  By `Spencer
+  Clark <https://github.com/spencerkclark>`_.
+
+
+v1.1.0 (May 31st, 2018)
+-----------------------
+
+Requirements
+~~~~~~~~~~~~
+
+* nc-time-axis now requires matplotlib of at least version 2.0 (:issue:`23`,
+  :pull:`34`).  By `Filipe Fernandes <https://github.com/ocefpaf>`_.
+* nc-time-axis now uses the standalone cftime package instead of the
+  ``netcdftime`` module formerly packaged in netcdf4-python (:pull:`30`,
+  :pull:`32`). By `Filipe Fernandes <https://github.com/ocefpaf>`_.
+
+Infrastructure
+~~~~~~~~~~~~~~
+
+* Updated continuous integration tests to be run with both Python 2 and Python 3
+  (:pull:`33`).  By `Filipe Fernandes <https://github.com/ocefpaf>`_.
+* Updated continuous integration to use ``install_requires`` information to
+  install dependencies of nc-time-axis instead of a requirements file
+  (:issue:`27`, :pull:`28`).  By `Phil Elson <https://github.com/pelson>`_.
+* Added installation and test requirements to ``setup.py`` (:pull:`26`).  By
+  `Luke Carroll <https://github.com/LukeC92>`_.
+
+Documentation
+~~~~~~~~~~~~~
+
+* Added installation instructions and a usage example to the README for display
+  on GitHub (:pull:`24`, :pull:`25`).  By `mbeedie
+  <https://github.com/mbeedie>`_.
+
+
+v1.0.2 (March 7th, 2017)
+------------------------
+
+Requirements
+~~~~~~~~~~~~
+
+* nc-time-axis now requires matplotlib less than version 2.0 (:pull:`22`).  By
+  `Mark Hedley <https://github.com/marqh>`_.
+
+Bug fixes
+~~~~~~~~~
+
+* Fixed a bug in comparing calendars (:pull:`22`).  By `Mark
+  Hedley <https://github.com/marqh>`_.
+
+
+v1.0.1 (November 23rd, 2016)
+----------------------------
+
+Bug fixes
+~~~~~~~~~
+
+* Fixed a bug the prevented converting NumPy arrays of datetime objects.  This
+  enables making Hovmoller diagrams using nc-time-axis.  By `Peter Killick
+  <https://github.com/DPeterK>`_.
+
+
+v1.0.0 (July 1st, 2016)
+-----------------------
+
+New features
+~~~~~~~~~~~~
+
+* Added the fundamental objects of nc-time-axis, i.e.
+  :py:class:`NetCDFTimeDateLocator`, :py:class:`NetCDFTimeDateFormatter`, and
+  :py:class:`NetCDFTimeConverter` (:pull:`2`).  By `lbdryer
+  <https://github.com/lbdreyer>`_ and `Phil Elson <https://github.com/pelson>`_.
+* Added unit and integration tests (:pull:`3`, :pull:`13`).  By `lbdryer
+  <https://github.com/lbdreyer>`_.
+* Added a ``__version__`` attribute to nc-time-axis (:pull:`9`).  By `lbdryer
+  <https://github.com/lbdreyer>`_.
+* Added the :py:class:`CalendarDateTime` class (:pull:`12`, :pull:`15`).  By
+  `lbdryer <https://github.com/lbdreyer>`_.
+
+Infrastructure
+~~~~~~~~~~~~~~
+
+* Added initial packaging infrastructure (:pull:`4`).  By `lbdryer
+  <https://github.com/lbdreyer>`_.
+* Configured continuous integration to be run using Travis CI (:pull:`6`).  By
+  `lbdryer <https://github.com/lbdreyer>`_.
+* Added test coverage computation and reporting (:pull:`17`, :pull:`19`). By
+  `lbdryer <https://github.com/lbdreyer>`_.
+
+Documentation
+~~~~~~~~~~~~~
+
+* Added a Travis CI badge to the README and convert to reStructuredText format
+  (:pull:`11`).  By `lbdryer <https://github.com/lbdreyer>`_.
+* Added basic description to the README (:pull:`5`).  By `Peter Killick
+  <https://github.com/DPeterK>`_.
+* Added contributing guidelines to the repository (:pull:`1`).  By
+  `lbdryer <https://github.com/lbdreyer>`_.
diff --git a/nc_time_axis/__init__.py b/nc_time_axis/__init__.py
index 22569e2..3f37c82 100644
--- a/nc_time_axis/__init__.py
+++ b/nc_time_axis/__init__.py
@@ -19,11 +19,31 @@ _TIME_UNITS = "days since 2000-01-01"
 
 class CalendarDateTime:
     """
-    Container for :class:`cftime.datetime` object and calendar.
+    Container for a :py:class:`cftime.datetime` object and calendar.
 
+    Parameters
+    ----------
+    datetime : :py:class:`cftime.datetime`
+        The datetime instance associated with this
+        :py:class:`CalendarDateTime` object.
+    calendar : str
+        The calendar type of the datetime object, e.g. ``"noleap"``.  See
+        :py:class:`cftime.datetime` documentation for a full list of valid
+        calendar strings.
+
+    Notes
+    -----
+    This class is no longer needed and will be deprecated in nc-time-axis
+    version 1.5.
     """
 
     def __init__(self, datetime, calendar):
+        warnings.warn(
+            "CalendarDateTime is obsolete and will be deprecated in nc_time_axis "
+            "version 1.5.  Please consider switching to plotting instances or "
+            "subclasses of cftime.datetime directly.",
+            DeprecationWarning,
+        )
         self.datetime = datetime
         self.calendar = calendar
 
@@ -56,8 +76,23 @@ _RESOLUTION_TO_FORMAT = {
 
 class AutoCFTimeFormatter(mticker.Formatter):
     """
-    Formatter for cftime.datetime data.
+    Automatic formatter for :py:class:`cftime.datetime` data.
+
+    Automatically chooses a date format based on the resolution set by the
+    :py:class:`NetCDFDateTimeLocator`.  If no resolution is set, a default
+    format of ``"%Y-%m-%d"`` is used.
 
+    Parameters
+    ----------
+    locator : NetCDFDateTimeLocator
+        The locator to be associated with this formatter.
+    calendar : str
+        The calendar type of the axis, e.g. ``"noleap"``.  See the
+        :py:class:`cftime.datetime` documentation for a full list of valid
+        calendar strings.
+    time_units : str, optional
+        The time units the numeric tick values represent.  Note this will
+        be deprecated in nc-time-axis version 1.5.
     """
 
     def __init__(self, locator, calendar, time_units=None):
@@ -96,14 +131,19 @@ class NetCDFTimeDateFormatter(AutoCFTimeFormatter):
 
 class CFTimeFormatter(mticker.Formatter):
     """
-    A formatter for explicitly setting the format of a cftime.datetime axis.
+    A formatter for explicitly setting the format of a
+    :py:class:`cftime.datetime` axis.
 
     Parameters
     ----------
-    format : str
-        Format string that can be passed to cftime.datetime.strftime, e.g. "%Y-%m-%d"
+    format : str Format string that can be passed to cftime.datetime.strftime,
+        e.g. ``"%Y-%m-%d"``.  See `the Python documentation
+        <https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes>`_
+        for acceptable format codes.
     calendar : str
-        Calendar type of the datetime axis
+        The calendar type of the axis, e.g. ``"noleap"``.  See the
+        :py:class:`cftime.datetime` documentation for a full list of valid
+        calendar strings.
     """
 
     def __init__(self, format, calendar):
@@ -117,8 +157,23 @@ class CFTimeFormatter(mticker.Formatter):
 
 class NetCDFTimeDateLocator(mticker.Locator):
     """
-    Determines tick locations when plotting cftime.datetime data.
+    Determines tick locations when plotting :py:class:`cftime.datetime` data.
 
+    Parameters
+    ----------
+    max_n_ticks : int
+        The maximum number of ticks along the axis.  This is passed internally
+        to a :py:class:`matplotlib.ticker.MaxNLocator` class.
+    calendar : str
+        The calendar type of the axis, e.g. ``"noleap"``.  See the
+        :py:class:`cftime.datetime` documentation for a full list of valid
+        calendar strings.
+    date_unit : str
+        The time units the numeric tick values represent.  Note this will
+        be deprecated in nc-time-axis version 1.5.
+    min_n_ticks : int, default 3
+        The minimum number of ticks along the axis. Note this is currently
+        not used.
     """
 
     real_world_calendars = (
@@ -291,7 +346,7 @@ class NetCDFTimeDateLocator(mticker.Locator):
 
 class NetCDFTimeConverter(mdates.DateConverter):
     """
-    Converter for cftime.datetime data.
+    Converter for :py:class:`cftime.datetime` data.
 
     """
 
@@ -362,7 +417,7 @@ class NetCDFTimeConverter(mdates.DateConverter):
     def convert(cls, value, unit, axis):
         """
         Converts value, if it is not already a number or sequence of numbers,
-        with :func:`cftime.date2num`.
+        with :py:func:`cftime.date2num`.
 
         """
         shape = None
diff --git a/pyproject.toml b/pyproject.toml
index 4ab8bd6..7de5ac5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -26,6 +26,7 @@ exclude = '''
     | buck-out
     | build
     | dist
+    | docs
   )/
 )
 '''
diff --git a/requirements/docs.yml b/requirements/docs.yml
new file mode 100644
index 0000000..b04aef7
--- /dev/null
+++ b/requirements/docs.yml
@@ -0,0 +1,31 @@
+name: ncta-dev
+
+channels:
+  - conda-forge
+
+dependencies:
+  - python 3.9
+
+# setup dependencies
+  - setuptools
+  - setuptools-scm
+
+# core dependencies
+  - cftime >=1.5
+  - matplotlib
+  - numpy
+
+# test dependencies
+  - codecov
+  - pytest >=6.0
+  - pytest-cov
+
+# dev dependencies
+  - pip
+  - pre-commit
+
+# docs dependencies
+  - ipython
+  - numpydoc
+  - sphinx
+  - sphinx_rtd_theme


Test patch:
diff --git a/nc_time_axis/tests/integration/test_plot.py b/nc_time_axis/tests/integration/test_plot.py
index 54f9f10..7698e91 100644
--- a/nc_time_axis/tests/integration/test_plot.py
+++ b/nc_time_axis/tests/integration/test_plot.py
@@ -1,6 +1,7 @@
 """Integration test for plotting data with non-gregorian calendar."""
 
 import unittest
+import warnings
 
 import matplotlib
 
@@ -24,6 +25,7 @@ class Test(unittest.TestCase):
         # in an odd state, so we make sure it's been disposed of.
         plt.close("all")
 
+    @pytest.mark.filterwarnings("ignore::DeprecationWarning")
     def test_360_day_calendar_CalendarDateTime(self):
         calendar = "360_day"
         datetimes = [
@@ -62,6 +64,7 @@ class Test(unittest.TestCase):
         with self.assertRaisesRegex(ValueError, "defined"):
             plt.plot(datetimes)
 
+    @pytest.mark.filterwarnings("ignore::DeprecationWarning")
     def test_fill_between(self):
         calendar = "360_day"
         dt = [
@@ -84,16 +87,19 @@ def teardown_function(function):
     plt.close()
 
 
-TICKS = {
-    "List[cftime.datetime]": [cftime.Datetime360Day(1986, 2, 1)],
-    "List[CalendarDateTime]": [
-        nc_time_axis.CalendarDateTime(
-            cftime.Datetime360Day(1986, 2, 1), "360_day"
-        )
-    ],
-}
+with warnings.catch_warnings():
+    warnings.filterwarnings("ignore", category=DeprecationWarning)
+    TICKS = {
+        "List[cftime.datetime]": [cftime.Datetime360Day(1986, 2, 1)],
+        "List[CalendarDateTime]": [
+            nc_time_axis.CalendarDateTime(
+                cftime.Datetime360Day(1986, 2, 1), "360_day"
+            )
+        ],
+    }
 
 
+@pytest.mark.filterwarnings("ignore::DeprecationWarning")
 @pytest.mark.parametrize("axis", ["x", "y"])
 @pytest.mark.parametrize("ticks", TICKS.values(), ids=list(TICKS.keys()))
 def test_set_ticks(axis, ticks):
diff --git a/nc_time_axis/tests/unit/test_CalendarDateTime.py b/nc_time_axis/tests/unit/test_CalendarDateTime.py
index 4d3ddaa..af2f66e 100644
--- a/nc_time_axis/tests/unit/test_CalendarDateTime.py
+++ b/nc_time_axis/tests/unit/test_CalendarDateTime.py
@@ -3,10 +3,12 @@
 import unittest
 
 import cftime
+import pytest
 
 from nc_time_axis import CalendarDateTime
 
 
+@pytest.mark.filterwarnings("ignore::DeprecationWarning")
 class Test___eq__(unittest.TestCase):
     def setUp(self):
         self.cdt = CalendarDateTime(
@@ -32,6 +34,7 @@ class Test___eq__(unittest.TestCase):
         self.assertFalse(self.cdt == "not a CalendarDateTime")
 
 
+@pytest.mark.filterwarnings("ignore::DeprecationWarning")
 class Test__ne__(unittest.TestCase):
     def setUp(self):
         self.cdt = CalendarDateTime(
@@ -57,5 +60,10 @@ class Test__ne__(unittest.TestCase):
         self.assertTrue(self.cdt != "not a CalendarDateTime")
 
 
+def test_CalendarDateTime_deprecation_warning():
+    with pytest.warns(DeprecationWarning, match="deprecated"):
+        CalendarDateTime(cftime.datetime(2000, 1, 1), "gregorian")
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/nc_time_axis/tests/unit/test_NetCDFTimeConverter.py b/nc_time_axis/tests/unit/test_NetCDFTimeConverter.py
index 935ffab..c33ceb2 100644
--- a/nc_time_axis/tests/unit/test_NetCDFTimeConverter.py
+++ b/nc_time_axis/tests/unit/test_NetCDFTimeConverter.py
@@ -4,10 +4,12 @@ import unittest
 
 import cftime
 import numpy as np
+import pytest
 
 from nc_time_axis import CalendarDateTime, NetCDFTimeConverter
 
 
+@pytest.mark.filterwarnings("ignore::DeprecationWarning")
 class Test_axisinfo(unittest.TestCase):
     def test_axis_default_limits(self):
         cal = "360_day"
@@ -24,6 +26,7 @@ class Test_axisinfo(unittest.TestCase):
 
 
 class Test_default_units(unittest.TestCase):
+    @pytest.mark.filterwarnings("ignore::DeprecationWarning")
     def test_360_day_calendar_point_CalendarDateTime(self):
         calendar = "360_day"
         unit = "days since 2000-01-01"
@@ -31,6 +34,7 @@ class Test_default_units(unittest.TestCase):
         result = NetCDFTimeConverter().default_units(val, None)
         self.assertEqual(result, (calendar, unit, CalendarDateTime))
 
+    @pytest.mark.filterwarnings("ignore::DeprecationWarning")
     def test_360_day_calendar_list_CalendarDateTime(self):
         calendar = "360_day"
         unit = "days since 2000-01-01"
@@ -38,6 +42,7 @@ class Test_default_units(unittest.TestCase):
         result = NetCDFTimeConverter().default_units(val, None)
         self.assertEqual(result, (calendar, unit, CalendarDateTime))
 
+    @pytest.mark.filterwarnings("ignore::DeprecationWarning")
     def test_360_day_calendar_nd_CalendarDateTime(self):
         # Test the case where the input is an nd-array.
         calendar = "360_day"
@@ -105,6 +110,7 @@ class Test_default_units(unittest.TestCase):
         result = NetCDFTimeConverter().default_units(val, None)
         self.assertEqual(result, (calendar, unit, cftime.datetime))
 
+    @pytest.mark.filterwarnings("ignore::DeprecationWarning")
     def test_nonequal_calendars(self):
         # Test that different supplied calendars causes an error.
         calendar_1 = "360_day"
@@ -146,6 +152,7 @@ class Test_convert(unittest.TestCase):
         result = NetCDFTimeConverter().convert(val, None, None)
         np.testing.assert_array_equal(result, val)
 
+    @pytest.mark.filterwarnings("ignore::DeprecationWarning")
     def test_cftime_CalendarDateTime(self):
         val = CalendarDateTime(cftime.datetime(2014, 8, 12), "365_day")
         result = NetCDFTimeConverter().convert(val, None, None)
@@ -195,6 +202,7 @@ class Test_convert(unittest.TestCase):
         assert result == expected
         assert len(result) == 1
 
+    @pytest.mark.filterwarnings("ignore::DeprecationWarning")
     def test_cftime_np_array_CalendarDateTime(self):
         val = np.array(
             [CalendarDateTime(cftime.datetime(2012, 6, 4), "360_day")],
@@ -215,6 +223,7 @@ class Test_convert(unittest.TestCase):
         result = NetCDFTimeConverter().convert(val, None, None)
         self.assertEqual(result, np.array([4473.0]))
 
+    @pytest.mark.filterwarnings("ignore::DeprecationWarning")
     def test_non_cftime_datetime(self):
         val = CalendarDateTime(4, "360_day")
         msg = (