-
-
Notifications
You must be signed in to change notification settings - Fork 8k
Description
Bug summary
When plotting by default at the end of a month, the date locator seems to jump a day, creating non regularly spaced ticks.
Code for reproduction
To create the plot which cause problem
import matplotlib.pyplot as plt
import pandas as pd
x = pd.to_datetime(['2024-07-29 00:12:00', '2024-08-10 19:13:00'])
y = [0, 1]
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(x, y)
ax.grid()
plt.show()
If we isolate the `AutoDateLocator` which causes the problem:
from matplotlib.dates import AutoDateLocator, DayLocator
import pandas as pd
x = pd.to_datetime(['2024-07-29 00:12:00', '2024-08-10 19:13:00'])
locator = AutoDateLocator()
best_locator = locator.get_locator(x.min(), x.max())
print(best_locator.tick_values(x.min(), x.max()))
Actual outcome
The locator object gives this output:
[19935. 19936. 19938. 19940. 19942. 19944.]
Which has a frequency of 2, excepted of 1 between the first points.

Expected outcome
I would expect to have a regularly spaced output.
Additional information
I had a look to the code (AutoDateLocator.get_locator
in matplotlib.dates
) and it seems that the problem is the range for 'weekly' frequency is defined as range(1, 32)
in the AutoDateLocator._by_ranges
.
Then below in the method, an interval is added to the range
byranges[i] = self._byranges[i][::interval]
But in the case of an end of the month, this can create an irregular range.
It would be better do define the range manually in this case.
I tried to apply the following fix which seems to work:
(in the get_locator
method, after the if i in (DAILY, WEEKLY):
statement)
if i == WEEKLY:
# Set the range with the correct number of days in the current month from dmin
year, month = dmin.year, dmin.month
# Calculate the number of days in the current month
next_year, next_month = (year + 1, 1) if month == 12 else (year, month + 1)
n_days_in_month = (datetime.date(next_year, next_month, 1) - datetime.date(year, month, 1)).days
start_day = dmin.day
total_days = (dmax - dmin).days + interval
byranges[i] = [
(d - 1) % n_days_in_month + 1
for d in range(start_day, start_day + total_days, interval)
]
Operating system
Windows
Matplotlib Version
3.10.3
Matplotlib Backend
No response
Python version
Python 3.13.5
Jupyter version
No response
Installation
None