Say a store owner in Denver wants six months of daily orders in Matplotlib, with dates on the x axis and one clean label per month. You call plot_date() because an old tutorial said so, and Python stops with an AttributeError.
That function is gone. It was discouraged for years, deprecated in Matplotlib 3.9 and removed in 3.11. Plain plot() has handled dates for a long time, so nothing was lost.
In this tutorial, I’ll show you how to control matplotlib dates on x axis charts with plot(), tick locators and date formatters, including the fix for labels that overlap.
Quick answer: pass real date objects to ax.plot(), set where ticks go with a locator, and set their text with a formatter:
ax.plot(dates, orders)
ax.xaxis.set_major_locator(mdates.MonthLocator())
ax.xaxis.set_major_formatter(mdates.DateFormatter("%b %Y"))
I tested every script below on Matplotlib 3.11.2 on Python 3.12.5, so the output blocks are real.
The sample orders data for the date axis
I’ll use one dataset all the way through: 180 days of made-up online orders for a Denver store, starting January 5, 2026. Save this file as store_orders.py, because every later script imports it.
import random
from datetime import date, timedelta
random.seed(2026)
start = date(2026, 1, 5)
dates = [start + timedelta(days=i) for i in range(180)]
orders = [round(120 + i * 0.4 + random.gauss(0, 12)) for i in range(180)]
if __name__ == "__main__":
print("Days:", len(dates))
print("First:", dates[0], "orders:", orders[0])
print("Last: ", dates[-1], "orders:", orders[-1])
Output:
Days: 180
First: 2026-01-05 orders: 130
Last: 2026-07-03 orders: 185

The dates list holds Python date objects, not text, and that detail matters later. For a broader look at this kind of chart, see my guide to the Matplotlib time series plot.
What happened to plot_date in Matplotlib?
Here’s the old call on the same data, so you can see the failure for yourself:
import matplotlib
import matplotlib.pyplot as plt
from store_orders import dates, orders
print("Matplotlib", matplotlib.__version__)
plt.plot_date(dates, orders)
Output (the error):
Matplotlib 3.11.2
Traceback (most recent call last):
File "C:\pyguides\plot_date_test.py", line 6, in <module>
plt.plot_date(dates, orders)
^^^^^^^^^^^^^
AttributeError: module 'matplotlib.pyplot' has no attribute 'plot_date'

AttributeError.If you’re on an older release, it may still run with a deprecation warning, but I wouldn’t write new code against it. The replacement is one small change, since plot_date() simply drew markers with no line by default.
import matplotlib.pyplot as plt
from store_orders import dates, orders
fig, ax = plt.subplots(figsize=(7, 3.6), layout="constrained")
ax.plot(dates, orders, "o", markersize=3) # plot_date's default look
ax.set_title("Denver store: daily online orders, 2026")
ax.set_ylabel("Orders")
print(type(ax.xaxis.get_major_locator()).__name__)
plt.show()
Output:
AutoDateLocator

plot() with an "o" format reproduces the old marker look.AutoDateLocator in the output confirms Matplotlib spotted the dates and switched the axis to date mode on its own. My older plot_date overview covers what the function used to do.
If your x values are already numbers from date2num(), tell the axis they’re dates before plotting. That’s the route the Matplotlib docs suggest:
import matplotlib.dates as mdates
import matplotlib.pyplot as plt
from store_orders import dates, orders
nums = mdates.date2num(dates) # plain floats, days since 1970
print("First value:", float(nums[0]))
fig, ax = plt.subplots(figsize=(7, 3.6), layout="constrained")
ax.xaxis_date() # treat the x values as dates
ax.plot(nums, orders)
print(type(ax.xaxis.get_major_locator()).__name__)
Output:
First value: 20458.0
AutoDateLocator
That first value counts days since January 1, 1970, and xaxis_date() turns those floats back into a date axis.
How do you show one x-axis tick per month?
Same orders, now with the labels the store owner asked for. A locator decides where ticks go, and a formatter decides what each one says. They’re separate jobs, which is why you set both.
import matplotlib.dates as mdates
import matplotlib.pyplot as plt
from store_orders import dates, orders
fig, ax = plt.subplots(figsize=(7, 3.6), layout="constrained")
ax.plot(dates, orders)
ax.xaxis.set_major_locator(mdates.MonthLocator())
ax.xaxis.set_major_formatter(mdates.DateFormatter("%b %Y"))
ticks = [mdates.num2date(t).strftime("%b %d") for t in ax.get_xticks()]
print(ticks)
plt.show()
Output:
['Jan 01', 'Feb 01', 'Mar 01', 'Apr 01', 'May 01', 'Jun 01', 'Jul 01']

MonthLocator puts a tick on the first of each month.That gives seven ticks, January 1 through July 1. January 1 shows up even though the data starts on the 5th, because Matplotlib pads the axis a little on each side.
The format string uses standard strftime codes: %b is the short month and %Y the four-digit year. There’s more on these codes in my post on date formats and conversion in Matplotlib.
Pro Tip: for every third month, use MonthLocator(interval=3). For fixed quarter starts, pass bymonth=[1, 4, 7, 10] instead.
Show weekly date ticks in Matplotlib with WeekdayLocator
Same data again, zoomed in to March. Here I want a labeled tick each Monday and a small unlabeled tick for every day between them:
from datetime import date
import matplotlib.dates as mdates
import matplotlib.pyplot as plt
from store_orders import dates, orders
fig, ax = plt.subplots(figsize=(7, 3.6), layout="constrained")
ax.plot(dates, orders, marker="o", markersize=3)
ax.set_xlim(date(2026, 3, 2), date(2026, 3, 30))
ax.xaxis.set_major_locator(mdates.WeekdayLocator(byweekday=mdates.MO))
ax.xaxis.set_minor_locator(mdates.DayLocator())
ax.xaxis.set_major_formatter(mdates.DateFormatter("%a %b %d"))
ticks = [mdates.num2date(t).strftime("%b %d") for t in ax.get_xticks()]
print(ticks)
plt.show()
Output:
['Mar 02', 'Mar 09', 'Mar 16', 'Mar 23', 'Mar 30']

The five major ticks land on March 2, 9, 16, 23 and 30. The zoom comes from passing date objects to set_xlim(), and my Matplotlib xlim guide covers that method in more detail.
Minor ticks don’t get text by default, so you don’t have to set a minor formatter here.
Set xticks on specific dates with custom labels
Locators follow a rule. When you need ticks on three particular days, list them yourself. Same orders, marked at this store’s three sale events:
from datetime import date
import matplotlib.pyplot as plt
from store_orders import dates, orders
sales = {date(2026, 2, 16): "Presidents Day",
date(2026, 4, 5): "Easter",
date(2026, 5, 25): "Memorial Day"}
fig, ax = plt.subplots(figsize=(7, 3.6), layout="constrained")
ax.plot(dates, orders)
ax.set_xticks(list(sales), labels=list(sales.values()))
ax.grid(axis="x", linestyle="--")
print(len(ax.get_xticks()), "ticks")
plt.show()
Output:
3 ticks

set_xticks() accepts date objects directly, and labels= replaces the date text in one call. I’d use this for events, not regular intervals, since the ticks won’t adapt if someone zooms.
To draw a stronger marker on one of those days, add a vertical line at a date.
Let ConciseDateFormatter pick the labels
Sometimes you don’t want to choose at all, especially when the date range changes from report to report. Same data, with the choice handed to Matplotlib:
import matplotlib.dates as mdates
import matplotlib.pyplot as plt
from store_orders import dates, orders
fig, ax = plt.subplots(figsize=(7, 3.6), layout="constrained")
ax.plot(dates, orders)
locator = mdates.AutoDateLocator(minticks=5, maxticks=9)
ax.xaxis.set_major_locator(locator)
ax.xaxis.set_major_formatter(mdates.ConciseDateFormatter(locator))
plt.show()

minticks and maxticks set the range of ticks you’ll accept. The formatter then repeats only what changes between ticks, which is why each label stays short. It’s my default for dashboards, where the window keeps moving.
Fix overlapping date labels with autofmt_xdate
Long labels collide fast. Same orders, with a tick every second Monday and the full date on each one. The only change between these two runs is the autofmt_xdate() line:
import matplotlib.dates as mdates
import matplotlib.pyplot as plt
from store_orders import dates, orders
fig, ax = plt.subplots(figsize=(7, 3.6))
ax.plot(dates, orders)
ax.set_xlim(dates[0], dates[-1])
every_2nd = mdates.WeekdayLocator(byweekday=mdates.MO, interval=2)
ax.xaxis.set_major_locator(every_2nd)
ax.xaxis.set_major_formatter(mdates.DateFormatter("%b %d, %Y"))
fig.autofmt_xdate() # rotate and right-align the date labels
plt.show()

autofmt_xdate() they’re readable.fig.autofmt_xdate() rotates the labels 30 degrees, right-aligns them and adds room at the bottom. If you use pyplot without a fig variable, plt.gcf().autofmt_xdate() does the same thing.
For an exact angle, pass rotation=45 to it. My post on how to rotate tick labels by 45 and 90 degrees goes further.
You’ll also notice one extra effect on stacked charts. When subplots share an x axis, autofmt_xdate() keeps date labels only on the bottom row, which suits multiple Matplotlib plots sharing one timeline.
Common errors with Matplotlib date axes and how to fix them
Text dates: if your dates are strings, Matplotlib treats them as categories. You get one evenly spaced tick per string, and the locators above stop working. This small script shows the difference:
from datetime import datetime
import matplotlib.pyplot as plt
import pandas as pd
raw = ["2026-03-02", "2026-03-09", "2026-03-16", "2026-03-23"]
y = [141, 150, 138, 162]
fig, ax = plt.subplots()
ax.plot(raw, y)
print("Text dates:", type(ax.xaxis.get_major_locator()).__name__)
fixed = [datetime.strptime(s, "%Y-%m-%d") for s in raw]
fig, ax = plt.subplots()
ax.plot(fixed, y)
print("strptime: ", type(ax.xaxis.get_major_locator()).__name__)
fig, ax = plt.subplots()
ax.plot(pd.to_datetime(raw), y)
print("pandas: ", type(ax.xaxis.get_major_locator()).__name__)
Output:
Text dates: StrCategoryLocator
strptime: AutoDateLocator
pandas: AutoDateLocator

Convert with datetime.strptime() or pd.to_datetime() first. The fix is the same whether the strings came from a CSV or an API.
Too many ticks: DayLocator on a long range tries to draw every day. Over 1,200 days it goes past the 1,000-tick limit, and Matplotlib logs a warning:
from datetime import date, timedelta
import matplotlib.dates as mdates
import matplotlib.pyplot as plt
days = [date(2026, 1, 1) + timedelta(days=i) for i in range(1200)]
fig, ax = plt.subplots()
ax.plot(days, range(1200))
ax.xaxis.set_major_locator(mdates.DayLocator())
print("DayLocator ticks:", len(ax.get_xticks()))
ax.xaxis.set_major_locator(mdates.AutoDateLocator())
print("AutoDateLocator ticks:", len(ax.get_xticks()))
Output:
Locator attempting to generate 1318 ticks ([20395.0, ..., 21712.0]), which exceeds Locator.MAXTICKS (1000).
DayLocator ticks: 1318
AutoDateLocator ticks: 11
Switching to AutoDateLocator brings the count back down to a readable handful.
Bars instead of lines: the same locators work on bar charts, but bar width is measured in days. I cover that in Matplotlib bar charts with dates.
Which date tick tool should you use?
| Goal | Use |
|---|---|
| One tick per month | MonthLocator + DateFormatter |
| One tick per week | WeekdayLocator(byweekday=MO) |
| Exact event dates | set_xticks(dates, labels=...) |
| Changing date ranges | AutoDateLocator + ConciseDateFormatter |
| Overlapping labels | fig.autofmt_xdate() |
Old plot_date code | ax.plot(dates, y, "o") |
Once the date axis behaves, these related Matplotlib tutorials are useful next steps:
- Plot multiple lines on a time series chart
- Scatter and multi-line charts with dates
- Change line style and color on date charts
Frequently asked questions
Is plot_date removed from Matplotlib?
Yes. It was deprecated in 3.9 and removed in 3.11, so calling it raises AttributeError. Use plot() instead.
How do I put dates on the x axis in Matplotlib?
Pass datetime or date objects to ax.plot(). Matplotlib detects them and switches the axis to date ticks automatically.
How do I change the date format on the x axis?
Call ax.xaxis.set_major_formatter(mdates.DateFormatter("%b %Y")) with any strftime codes you need.
What does autofmt_xdate do?
It rotates and right-aligns date tick labels and makes room for them at the bottom of the figure.
Why does Matplotlib show every date string on the axis?
Text values are treated as categories. Convert them with pd.to_datetime() or datetime.strptime() first.
Where are the date tick options documented?
See the matplotlib.dates reference in the official docs.

Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn