Repository: matplotlib/matplotlib

Issue:
plot_date() ignores timezone in matplotlib version 2.0.0
### Bug report

**Bug summary**

- It is a reopen bug report, same to #5575. The bug still exists for version 2.0.0.

copy paste the code from #5575 

```python
import pytz
import matplotlib.pyplot as plt
from datetime import datetime
time_index = [pytz.timezone('Europe/Berlin').localize(datetime(year=2015, month=11, day=27, hour=x)) for x in range(4)]
plt.plot_date(time_index, [4]*4, tz='Europe/Berlin')
plt.plot_date(time_index, [3]*4, tz='UTC')
```

**Actual outcome**

- The output gives no difference in xaxis, implying tz is not working.


Discussion:
For reference, this works fine if the two plots are on different axes, which is all #6091 tests for. The problem happens if two plots with different timezones are done on the same axis.
What timezone would you expect the axes to be in if you pass in data with different timezones (which may be considered 'different units')?
It seems natural enough to convert everything to the axis' tz?
Is there any confirmation on whether this is going to be fixed in v3.0?
I don't think there is a fix for this right now.  OTOH, I suspect its not too hard to do.  PR would be welcome...
Actually, I take that back.  The problem is that `axis.update_units` only checks that the converter is appropriate for the data, not the actual units of the data. So the fix is to fix the units machinery, but thats under review.  
Thanks for providing this information. Looking forward to the fix!
I agree that the documentation shouldn't claim that this works.  Not sure how we indicate that a kwarg actually has no effect, but that we'd like it to someday...

I wouldn't expect this in the forseable future, as it gets at the root of our units handling, which is currently not set up to change units on the fly.  Safest is to convert all your datetime objects to one timezone and then plot.
This issue has been marked "inactive" because it has been 365 days since the last comment. If this issue is still present in recent Matplotlib releases, or the feature request is still wanted, please leave a comment and this label will be removed. If there are no updates in another 30 days, this issue will be automatically closed, but you are free to re-open or create a new issue if needed. We value issue reports, and this procedure is meant to help us resurface and prioritize issues that have not been addressed yet, not make them disappear.  Thanks for your help!
Still seems to be an issue on current main, so I'll keep this open.
Since plot_date is discouraged anyway, it’s unlikely that we’ll change behavior.

I suggest we close this by better documenting, that tz is only used for  [axis_date](https://matplotlib.org/stable/api/_as_gen/matplotlib.axis.Axis.axis_date.html#matplotlib.axis.Axis.axis_date), so affects the axis labeling, but does not directly imply the timezone of the data.
I'm guessing this is still and issue with plain `plot()` though, and I don't see why taking the timezone into account when doing the unit conversion isn't a valid feature request (or even a bug fix)?
Ok, but then let’s rephrase this into a new issue using `plot` only.
With v3.8.2 I have

```python
import pytz
import matplotlib.pyplot as plt
from datetime import datetime

time_index_berlin = [pytz.timezone('Europe/Berlin').localize(datetime(year=2015, month=11, day=27, hour=x)) for x in range(4)]
time_index_utc = [pytz.timezone('UTC').localize(datetime(year=2015, month=11, day=27, hour=x)) for x in range(4)]


fig, ax = plt.subplots()

ax.plot(time_index_berlin, [4]*4, label='Berlin')
ax.plot(time_index_utc, [3]*4, label='UTC')

ax.legend()
fig.autofmt_xdate()

plt.show()
```
![image](https://github.com/matplotlib/matplotlib/assets/10599679/9e9145f0-6732-4ea9-8438-b3dc3f27374d)

If I reverse the order of plotting:

```python
ax.plot(time_index_utc, [3]*4, label='UTC')
ax.plot(time_index_berlin, [4]*4, label='Berlin')
```

![image](https://github.com/matplotlib/matplotlib/assets/10599679/c463e0e5-8011-4bd8-8b47-971e95243c88)

So it seems we plot in whichever timezone came first.
> it seems we plot in whichever timezone came first.

Which is reasonable. So, if I understand correctly, we don’t have an issue with pure `plot()`.
Ah fair enough, sorry for not checking... if we're not fixing anything on `plot_date()` now happy for this to be closed as wont fix 👍 
I'm confused - are we saying `plot_date` is broken, but `plot` works?  I wasn't aware `plot_date` did anything different that `plot`.  
Yet again, my bad for not actually checking... `plot_date` does seem to be working fine if given dates with different timezones. But this issue is about the `tz` argument to `plot_date` which AFAIK has no equivalent with `plot`?

(as an aside, in the above plot https://github.com/matplotlib/matplotlib/issues/8072#issuecomment-1919262892 I'm a bit confused, isn't Berlin one hour ahead of UTC, which means the lines are the wrong way round (Berlin should be one hour to the right of UTC instead of one hour to the left)??)
Confirmed, `plot` errors if you pass it the _tz_ parameter.

Yes, Berlin is an hour ahead of UTC (in winter).  So when it's midnight in Berlin, it's only 11pm UTC.  So I think the plot is correct (note both lines start at midnight).
https://github.com/matplotlib/matplotlib/blob/41d4149f84b40d3a90a70f81b940985b8485f04a/lib/matplotlib/axes/_axes.py#L1820-L1824

This is the implementation of `plot_date` as it stands.

The consequences of this implementation are as follows:

- Only the last `tz` passed is used and is used to interpret _all_ floating point inputs (regardless of what `tz` was passed with a particular floating point input)
   - we do not add the tz to the source data, just set it as the active interpretation of floats
- If no `tz` is provided, it will use the :rc:`timezone` (defaults to UTC)
- `datetime` objects will still translate to the timezone as they hold their timezone info themselves
- `datetime64` does _not_ store timezone info (though will parse it)... there is some handling of `datetime64` in `date2num`/`num2date` that looks like there either _was_ once some tz info or _is_ on some sub dtypes we support (pandas?, not sure, but `astimezone` is not a numpy thing)
   - As such, I believe they are always interpreted as UTC, but might be :rc:`timezone`, didn't spend the time to differentiate that.
- `float` inputs _will_ change (since changing the tz changes what the floating point number isformatted as/translates to as a date, and the tz is not attached to it.

The equivalent to passing the `tz` argument is to call `ax.xaxis_date(tz)` (or y axis, but by default x is the only one set).

So, in the example, both plots are using `datetime` objects which _are_ carrying timezone information as the `x` values... I do _not_ think we should be applying a new timezone to them in `plot_date` when they are given as timezoned values. I could see _an_ argument for attaching the tz to float inputs (e.g. by instead of doing `self.plot(x, y, **kwargs)` doing `self.plot(num2date(x, tz=tz), y, **kwargs)` (modulo x and y bool flag handling...) but even that I would think would only apply to _float_ inputs, not `datetime` inputs as is here... and even then am not sure it's actually expected behavior)

Given the admonition that very explicitly states "Do not use this for `datetime` inputs, use `plot` instead" (in current mpl, such text was not present when this issue was opened in fairness), my opinion is that this should be closed as "expected behavior". The admonition (which was added in #18346) also does give information about calling `axis_date` (though does reach in to the `axis` instead of using the `axes` method which exists... but just is a wrapper so doesn't really change anything)
I do wonder if we should go a step further and hard deprecate this method.  Datetime with timezones are confusing enough without slightly different semantics.  


From the discussion at #18346, it seems the intention at the time had been to pre-deprecate `plot_date` but the `PendingDeprecationWarning` was causing an issue with Sphinx :eyes: 
I'm fine with deprecating now. We've even already announced that we we will do this in the admonition at https://matplotlib.org/devdocs/api/_as_gen/matplotlib.axes.Axes.plot_date.html#matplotlib.axes.Axes.plot_date



Patch:
diff --git a/doc/api/next_api_changes/deprecations/27850-REC.rst b/doc/api/next_api_changes/deprecations/27850-REC.rst
new file mode 100644
index 000000000000..2021c2737ecd
--- /dev/null
+++ b/doc/api/next_api_changes/deprecations/27850-REC.rst
@@ -0,0 +1,10 @@
+``plot_date``
+~~~~~~~~~~~~~
+
+Use of `~.Axes.plot_date` has been discouraged since Matplotlib 3.5 and the
+function is now formally deprecated.
+
+- ``datetime``-like data should directly be plotted using `~.Axes.plot`.
+-  If you need to plot plain numeric data as :ref:`date-format` or need to set
+   a timezone, call ``ax.xaxis.axis_date`` / ``ax.yaxis.axis_date`` before
+   `~.Axes.plot`. See `.Axis.axis_date`.
diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py
index 60921f69fc1b..3a1cc2fb9fb6 100644
--- a/lib/matplotlib/axes/_axes.py
+++ b/lib/matplotlib/axes/_axes.py
@@ -1742,17 +1742,17 @@ def plot(self, *args, scalex=True, scaley=True, data=None, **kwargs):
             self._request_autoscale_view("y")
         return lines
 
+    @_api.deprecated("3.9", alternative="plot")
     @_preprocess_data(replace_names=["x", "y"], label_namer="y")
     @_docstring.dedent_interpd
     def plot_date(self, x, y, fmt='o', tz=None, xdate=True, ydate=False,
                   **kwargs):
         """
-        [*Discouraged*] Plot coercing the axis to treat floats as dates.
+        Plot coercing the axis to treat floats as dates.
 
-        .. admonition:: Discouraged
+        .. deprecated:: 3.9
 
-            This method exists for historic reasons and will be deprecated in
-            the future.
+            This method exists for historic reasons and will be removed in version 3.11.
 
             - ``datetime``-like data should directly be plotted using
               `~.Axes.plot`.


Test patch:
diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py
index 8c43b04e0eb7..fbcafa76dcea 100644
--- a/lib/matplotlib/tests/test_axes.py
+++ b/lib/matplotlib/tests/test_axes.py
@@ -870,7 +870,8 @@ def test_single_date():
     data1 = [-65.54]
 
     fig, ax = plt.subplots(2, 1)
-    ax[0].plot_date(time1 + dt, data1, 'o', color='r')
+    with pytest.warns(mpl.MatplotlibDeprecationWarning):
+        ax[0].plot_date(time1 + dt, data1, 'o', color='r')
     ax[1].plot(time1, data1, 'o', color='r')
 
 
@@ -6897,11 +6898,13 @@ def test_date_timezone_x():
     # Same Timezone
     plt.figure(figsize=(20, 12))
     plt.subplot(2, 1, 1)
-    plt.plot_date(time_index, [3] * 3, tz='Canada/Eastern')
+    with pytest.warns(mpl.MatplotlibDeprecationWarning):
+        plt.plot_date(time_index, [3] * 3, tz='Canada/Eastern')
 
     # Different Timezone
     plt.subplot(2, 1, 2)
-    plt.plot_date(time_index, [3] * 3, tz='UTC')
+    with pytest.warns(mpl.MatplotlibDeprecationWarning):
+        plt.plot_date(time_index, [3] * 3, tz='UTC')
 
 
 @image_comparison(['date_timezone_y.png'])
@@ -6914,12 +6917,13 @@ def test_date_timezone_y():
     # Same Timezone
     plt.figure(figsize=(20, 12))
     plt.subplot(2, 1, 1)
-    plt.plot_date([3] * 3,
-                  time_index, tz='Canada/Eastern', xdate=False, ydate=True)
+    with pytest.warns(mpl.MatplotlibDeprecationWarning):
+        plt.plot_date([3] * 3, time_index, tz='Canada/Eastern', xdate=False, ydate=True)
 
     # Different Timezone
     plt.subplot(2, 1, 2)
-    plt.plot_date([3] * 3, time_index, tz='UTC', xdate=False, ydate=True)
+    with pytest.warns(mpl.MatplotlibDeprecationWarning):
+        plt.plot_date([3] * 3, time_index, tz='UTC', xdate=False, ydate=True)
 
 
 @image_comparison(['date_timezone_x_and_y.png'], tol=1.0)
@@ -6932,11 +6936,13 @@ def test_date_timezone_x_and_y():
     # Same Timezone
     plt.figure(figsize=(20, 12))
     plt.subplot(2, 1, 1)
-    plt.plot_date(time_index, time_index, tz='UTC', ydate=True)
+    with pytest.warns(mpl.MatplotlibDeprecationWarning):
+        plt.plot_date(time_index, time_index, tz='UTC', ydate=True)
 
     # Different Timezone
     plt.subplot(2, 1, 2)
-    plt.plot_date(time_index, time_index, tz='US/Eastern', ydate=True)
+    with pytest.warns(mpl.MatplotlibDeprecationWarning):
+        plt.plot_date(time_index, time_index, tz='US/Eastern', ydate=True)
 
 
 @image_comparison(['axisbelow.png'], remove_text=True)
diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py
index 104a649e1464..4b693eb7d1ca 100644
--- a/lib/matplotlib/tests/test_datetime.py
+++ b/lib/matplotlib/tests/test_datetime.py
@@ -657,9 +657,10 @@ def test_plot_date(self):
         x_ranges = np.array(range(1, range_threshold))
         y_ranges = np.array(range(1, range_threshold))
 
-        ax1.plot_date(x_dates, y_dates)
-        ax2.plot_date(x_dates, y_ranges)
-        ax3.plot_date(x_ranges, y_dates)
+        with pytest.warns(mpl.MatplotlibDeprecationWarning):
+            ax1.plot_date(x_dates, y_dates)
+            ax2.plot_date(x_dates, y_ranges)
+            ax3.plot_date(x_ranges, y_dates)
 
     @pytest.mark.xfail(reason="Test for quiver not written yet")
     @mpl.style.context("default")
diff --git a/lib/mpl_toolkits/axes_grid1/tests/test_axes_grid1.py b/lib/mpl_toolkits/axes_grid1/tests/test_axes_grid1.py
index 328bb6e33fdf..b1a18d77747e 100644
--- a/lib/mpl_toolkits/axes_grid1/tests/test_axes_grid1.py
+++ b/lib/mpl_toolkits/axes_grid1/tests/test_axes_grid1.py
@@ -93,7 +93,8 @@ def test_twin_axes_empty_and_removed():
 
 def test_twin_axes_both_with_units():
     host = host_subplot(111)
-    host.plot_date([0, 1, 2], [0, 1, 2], xdate=False, ydate=True)
+    with pytest.warns(mpl.MatplotlibDeprecationWarning):
+        host.plot_date([0, 1, 2], [0, 1, 2], xdate=False, ydate=True)
     twin = host.twinx()
     twin.plot(["a", "b", "c"])
     assert host.get_yticklabels()[0].get_text() == "00:00:00"