Skip to content

[Bug]: AutoDateLocator is not regular at the end of the month #30395

@lionel42

Description

@lionel42

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.

Image

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions