How do I format dates on the x-axis of a matplotlib chart?
Short answer
Use matplotlib.dates locators and formatters to control the tick spacing and label format, and pass real datetime values, not strings. Rotate crowded labels or use autofmt_xdate to stop them overlapping.
The short version
Plot real datetime values (not strings), then control the tick labels with a formatter from matplotlib.dates, and the tick spacing with a locator. Rotate the labels or call fig.autofmt_xdate() when they crowd together. Passing dates as plain strings is the usual cause of a messy or wrongly ordered axis.
Setting the label format
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import pandas as pd
dates = pd.date_range("2021-01-01", periods=90)
values = range(90)
fig, ax = plt.subplots()
ax.plot(dates, values)
ax.xaxis.set_major_formatter(mdates.DateFormatter("%b %Y"))
plt.show()
DateFormatter("%b %Y") renders each tick as, for example, Jan 2021, using the same format codes as strftime. This replaces the default, sometimes overly precise, labels with the level of detail you want.
Controlling how many ticks appear
A formatter changes how ticks look; a locator changes where they are. Pair them so you get a sensible number of ticks at meaningful positions.
ax.xaxis.set_major_locator(mdates.MonthLocator()) # one tick per month
ax.xaxis.set_major_locator(mdates.MonthLocator(interval=3)) # every third month
Other locators include DayLocator, WeekdayLocator and YearLocator, so you can match the tick density to the span of your data.
Fixing overlapping labels
Long date labels often overlap. Rotate them, or let matplotlib arrange them for you.
fig.autofmt_xdate() # rotates and aligns automatically
# or manually
plt.setp(ax.get_xticklabels(), rotation=45, ha="right")
autofmt_xdate is the quick fix; the manual version gives you control over the angle and alignment.
The string trap
If you pass dates as text, matplotlib treats them as plain categories, one tick per string, in the order given rather than in time order, and none of the date locators or formatters apply. Convert to real datetimes first.
dates = pd.to_datetime(df["date"]) # then plot dates, not df["date"] as text
This single step fixes a whole class of "my date axis is wrong" problems, because only real datetimes get proper spacing and ordering.
Concise formatting as an easier default
For many cases, ConciseDateFormatter chooses sensible labels automatically, showing just enough detail and avoiding repetition.
locator = mdates.AutoDateLocator()
ax.xaxis.set_major_locator(locator)
ax.xaxis.set_major_formatter(mdates.ConciseDateFormatter(locator))
This is a good default when you do not want to hand-pick a format, because it adapts the labels to the range on screen.