Skip to content

Note

Click here to download the full example code

Uniform Business Hours For All Busdays

If the business of interest is only active during the same hours each day, such as with overnight gaps and pre/post-market time, pass a bushours tuple to collapse all off-hours.

  • All weekdays use the same bushours.
  • The standard Mon–Fri weekmask still applies so weekends remain collapsed.
  • Boundaries accept numeric hours, ISO time strings, or datetime.time objects — the following are all equivalent:
bushours=(9, 17)
bushours=("09:00", "17:00")
bushours=(datetime.time(9), datetime.time(17))

Core code:

ax.set_xscale("busday", bushours=(9, 17))

Business Hours (9:00–17:00), default linear matplotlib scale, using .set_xscale(('busday', bushours=(9, 17))

import matplotlib.dates as mdates
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

import busdayaxis

busdayaxis.register_scale()

# define dummy data
OPEN = 9
CLOSE = 17
num_days = 3
dates = pd.date_range("2025-01-06", periods=num_days * 24, freq="h")
returns = np.random.normal(0, 0.002, len(dates))
returns[~np.is_busday(np.array(dates, dtype="datetime64[D]"))] = 0.0
returns[(dates.hour <= OPEN) | (dates.hour > CLOSE)] = 0.0
prices = (1 + pd.Series(returns, index=dates)).cumprod()


fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(8, 7), sharey=True)
fig.suptitle(f"Business Hours ({OPEN}:00–{CLOSE}:00)", fontsize=14)

# axis with default linear scale
ax1.plot(dates, prices.values, linewidth=1.3)
ax1.set_title("default linear matplotlib scale")
ax1.set_ylabel("Price")
ax1.xaxis.set_major_locator(mdates.HourLocator())
ax1.xaxis.set_major_formatter(mdates.DateFormatter("%a %H"))
ax1.tick_params(axis="x", rotation=90)


# axis with business scale
ax2.plot(dates, prices.values, linewidth=1.3)
ax2.set_title(f"using `.set_xscale(('busday', bushours=({OPEN}, {CLOSE}))`")
ax2.set_ylabel("Price")
ax2.tick_params(axis="x", rotation=90)
ax2.set_xscale("busday", bushours=(OPEN, CLOSE))
ax2.xaxis.set_major_locator(busdayaxis.HourLocator())
ax2.xaxis.set_major_formatter(mdates.DateFormatter("%a %H"))

# Shade pre/post-market
full_days = pd.date_range(dates.min().normalize(), dates.max().normalize(), freq="D")
for d in full_days:
    ax1.axvspan(d, d + pd.Timedelta(hours=OPEN), color="grey", alpha=0.15, linewidth=0)
    ax1.axvspan(
        d + pd.Timedelta(hours=CLOSE),
        d + pd.Timedelta(hours=24),
        color="grey",
        alpha=0.15,
        linewidth=0,
    )
# Mark open/close boundaries
for d in full_days:
    ax2.axvline(d + pd.Timedelta(hours=OPEN), linestyle="--", linewidth=0.8, alpha=0.6)
    ax2.axvline(d + pd.Timedelta(hours=CLOSE), linestyle="--", linewidth=0.8, alpha=0.6)

_ = plt.tight_layout(rect=[0, 0, 1, 0.96])

Total running time of the script: ( 0 minutes 0.688 seconds)

Download Python source code: plot_2_bushours.py

Download Jupyter notebook: plot_2_bushours.ipynb

Gallery generated by mkdocs-gallery