Skip to content

API Reference

Scale

BusdayScale

BusdayScale(
    axis,
    bushours=(0, 24),
    weekmask=None,
    holidays=None,
    busdaycal=None,
)

Bases: mscale.ScaleBase

Matplotlib scale that compresses off-hours and non-business days.

Maps datetime values to business-day coordinates. Days and hours outside the defined schedule are collapsed so that every visible unit on the axis corresponds to active time.

Registered as the "busday" scale automatically on import busdayaxis. All parameters below except axis can be passed directly to ax.set_xscale("busday", ...). axis is injected automatically by Matplotlib and must not be passed explicitly.

PARAMETER DESCRIPTION
axis

Injected automatically by Matplotlib. Do not pass this via ax.set_xscale.

TYPE: matplotlib.axis.Axis

bushours
    | list[tuple[HourValue, HourValue]]
    | Mapping[WeekdayKey, tuple[HourValue, HourValue]
              | list[tuple[HourValue, HourValue]]]
    , optional

Active hours per weekday. Gaps between intervals (e.g. a lunch break) are collapsed on the axis just like overnight gaps or weekends.

HourValue is int | float | str | datetime.time. Strings must be valid ISO time strings (e.g. "09:30"). Numbers are hours since midnight (e.g. 9.5 = 09:30). WeekdayKey means either weekday index 0..6 or weekday name "Mon".."Sun".

Two accepted forms:

  • (start, end) or [(start, end), ...]: One or more sessions applied uniformly to all days. A plain (start, end) tuple is shorthand for a single-element list. Intervals must be sorted and non-overlapping. The weekmask still applies, so off-days (Sat/Sun by default) are collapsed regardless. Default (0, 24).

    Example:

    bushours=(9, 17)                    # numeric hours
    bushours=("09:00", "17:00")         # ISO time strings
    bushours=(dt.time(9), dt.time(17))  # datetime.time objects
    

    Multiple intervals — collapse a lunch break:

    bushours=[(9, 12), (13, 17)]
    

    To apply also on weekends, use:

    bushours=(9, 17), weekmask="1111111"  # Mon–Sun
    
  • Mapping[WeekdayKey, (start, end) | [(start, end), ...]]: Per-day schedule; keys are integers 0..6 or names "Mon""Sun". Each value is either a single (start, end) tuple or a list of tuples. Defaults for unspecified days:

    Day Key Default
    Mon 0 [(0, 24)]
    Tue 1 [(0, 24)]
    Wed 2 [(0, 24)]
    Thu 3 [(0, 24)]
    Fri 4 [(0, 24)]
    Sat 5 []
    Sun 6 []

    The weekmask is derived automatically: days with at least one interval are treated as business days, so passing {"Sun": (10, 18)} includes Sundays without a separate weekmask override.

    Example:

    bushours={"Sun": (10, 18)}  # Sundays with custom hours, weekdays 00-24
    

    Lunch break on Monday, early Friday close:

    bushours={"Mon": [(9, 12), (13, 17)], "Fri": (9, 13)}
    

    FX Trading Hours:

    bushours={
        "Sun": (22, 24),
        # Monday - Thursday 00:00-24:00
        "Fri": (0, 22),
    }
    

TYPE: tuple[HourValue, HourValue] DEFAULT: (0, 24)

weekmask

Which weekdays are business days ("1" = on, "0" = off). Passed to :func:numpy.is_busday. When None (default):

  • For dict bushours: derived automatically — days with at least one interval become business days.
  • For uniform bushours (tuple or list of tuples): "1111100" (Mon–Fri).

Use a string of 7 characters (Mon–Sun), a space-separated list of three-letter day names, or any format accepted by numpy.is_busday:

weekmask="1111100"              # Mon–Fri (default)
weekmask="Mon Tue Wed Thu Fri"  # equivalent
weekmask="Sun Mon Tue Wed Thu"  # Middle-Eastern work week
weekmask="1111111"              # every day is a business day

TYPE: str, array-like, or None DEFAULT: None

holidays

Extra non-business dates, regardless of weekmask. Dates on these days are collapsed to zero width on the axis, identical to weekends. Accepts any format understood by :func:numpy.is_busday (ISO strings, datetime.date, numpy.datetime64). Default None.

holidays=["2025-01-01", "2025-12-25"]

TYPE: array - like or None DEFAULT: None

busdaycal

Pre-built calendar combining a weekmask and holiday list. When set, weekmask and holidays are ignored. Useful when reusing the same calendar across multiple axes. Default None.

Note that busdaycal only controls which days are business days; it does not affect bushours. The two parameters are independent.

cal = np.busdaycalendar(weekmask="Mon Tue Wed Thu Fri",
                        holidays=["2025-01-01"])
ax.set_xscale("busday", busdaycal=cal)

TYPE: numpy.busdaycalendar or None DEFAULT: None

Notes
  • Clipping: Timestamps outside bushours are clipped to the nearest session boundary during the forward transform. For example, with bushours=(9, 17), both 08:30 and 09:00 map to the same axis position (the session open). With multiple intervals, times in a gap (e.g. during a lunch break) are clipped to the end of the preceding interval. The transform is therefore not perfectly invertible: the inverse always returns a time on a session boundary or within a session.
  • Timezone handling: The scale expects timezone-naive inputs and treats them as wall-clock time. If your data carries timezone info (e.g. a UTC-aware pandas.DatetimeIndex), convert to the market's local timezone and strip the tzinfo before plotting::

    dates = dates_utc.tz_convert("America/New_York").tz_localize(None)

Examples:

Compress weekends only (Mon–Fri default):

ax.set_xscale("busday")

Compress overnight gaps as well as weekends:

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

Collapse a lunch break (12:00–13:00) in addition to overnight gaps:

ax.set_xscale("busday", bushours=[(9, 12), (13, 17)])

Per-day hours — Friday early close, Monday with a lunch break:

ax.set_xscale("busday", bushours={"Mon": [(9, 12), (13, 17)], "Fri": (9, 13)})

Show Sundays with custom hours; weekmask derived automatically (Sat excluded):

ax.set_xscale("busday", bushours={"Sun": (10, 18)})

FX-style session: Sunday open 22:00, Friday close 22:00, full days otherwise:

ax.set_xscale("busday", bushours={"Sun": (22, 24), "Fri": (0, 22)})

Middle-Eastern work week (Sun–Thu) with a holiday:

ax.set_xscale("busday", weekmask="Sun Mon Tue Wed Thu",
              holidays=["2025-01-01"])

Reuse a pre-built numpy.busdaycalendar:

cal = np.busdaycalendar(weekmask="1111100", holidays=["2025-12-25"])
ax.set_xscale("busday", busdaycal=cal)

Custom tick placement with BusdayLocator:

import matplotlib.dates as mdates
ax.set_xscale("busday", bushours=(9, 17))
ax.xaxis.set_major_locator(
    BusdayLocator(mdates.HourLocator(byhour=range(9, 18)))
)

register_scale

register_scale()

Register the "busday" scale with Matplotlib.

Called automatically on import busdayaxis, so explicit calls are not required. The function is idempotent — calling it again has no effect and existing code that calls it explicitly continues to work.

After registration, ax.set_xscale("busday", ...) is available for the lifetime of the Python session. The keyword arguments bushours, weekmask, holidays, and busdaycal are forwarded directly to busdayaxis.BusdayScale; axis is injected by Matplotlib automatically and cannot be passed.

Examples:

import busdayaxis          # scale is registered automatically
import matplotlib.pyplot as plt
import pandas as pd

dates = pd.date_range("2025-01-01", periods=10, freq="D")
values = range(10)

fig, ax = plt.subplots()
ax.plot(dates, values)
ax.set_xscale("busday", bushours=(9, 17))  # kwargs forwarded to BusdayScale
plt.show()

Locator

BusdayLocator

BusdayLocator(base_locator=None, keep_midnight_ticks=None)

Bases: mdates.DateLocator

Tick locator that filters out ticks outside business hours and business days.

Wraps any Matplotlib date locator and discards ticks that fall on non-business days or outside the active session defined by bushours.

The locator reads the business-hours and weekmask configuration from the axis automatically (set by BusdayScale), so it stays in sync with the scale without any extra configuration.

The "busday" scale is registered automatically on import busdayaxis. BusdayLocator is set automatically when you call ax.set_xscale("busday").

PARAMETER DESCRIPTION
base_locator

The underlying datetime locator that proposes tick candidates. If None, falls back to AutoDateLocator.

TYPE: matplotlib.dates.DateLocator DEFAULT: None

keep_midnight_ticks

Controls whether ticks at midnight (00:00) on business days are kept even when they fall outside bushours. When None (default), this is determined automatically: midnight ticks are kept for daily-granularity locators (e.g. DayLocator) so that day labels remain properly aligned, and suppressed for finer locators (e.g. HourLocator) where they would appear outside the visible session.

TYPE: bool or None DEFAULT: None

Examples:

Hourly ticks during business hours (9–17)::

ax.set_xscale("busday", bushours=(9, 17))
ax.xaxis.set_major_locator(
    BusdayLocator(mdates.HourLocator())
)

Daily ticks on business days only::

ax.xaxis.set_major_locator(BusdayLocator(mdates.DayLocator()))

DayLocator

DayLocator(keep_midnight_ticks=None, **kwargs)

Bases: BusdayLocator

Business-day-aware wrapper around matplotlib.dates.DayLocator.

Places one tick per day (or every interval days), then discards any that fall on non-business days. Midnight ticks are always kept because daily ticks are placed at day boundaries.

All keyword arguments are forwarded to ~matplotlib.dates.DayLocator.

HourLocator

HourLocator(keep_midnight_ticks=None, **kwargs)

Bases: BusdayLocator

Business-day-aware wrapper around matplotlib.dates.HourLocator.

Places ticks at the specified hours, then discards any that fall outside business days or business hours. Midnight ticks are not kept by default because hourly ticks are sub-daily.

All keyword arguments are forwarded to ~matplotlib.dates.HourLocator.

MicrosecondLocator

MicrosecondLocator(keep_midnight_ticks=None, **kwargs)

Bases: BusdayLocator

Business-day-aware wrapper around matplotlib.dates.MicrosecondLocator.

Places ticks at the specified microseconds, then discards any that fall outside business days or business hours. Midnight ticks are not kept by default because microsecond-level ticks are sub-daily.

All keyword arguments are forwarded to ~matplotlib.dates.MicrosecondLocator.

MidBusdayLocator

Bases: mdates.DateLocator

Places one tick at the midpoint of the business hours for each business day.

Useful for centering day labels within each session, even when business hours vary by weekday or differ from the standard 9–17 window.

Examples:

Center day labels as minor ticks::

ax.set_xscale("busday", bushours=(9, 17))
ax.xaxis.set_minor_locator(busdayaxis.MidBusdayLocator())
ax.xaxis.set_minor_formatter(mdates.DateFormatter("%a"))

MinuteLocator

MinuteLocator(keep_midnight_ticks=None, **kwargs)

Bases: BusdayLocator

Business-day-aware wrapper around matplotlib.dates.MinuteLocator.

Places ticks at the specified minutes, then discards any that fall outside business days or business hours. Midnight ticks are not kept by default because minute-level ticks are sub-daily.

All keyword arguments are forwarded to ~matplotlib.dates.MinuteLocator.

SecondLocator

SecondLocator(keep_midnight_ticks=None, **kwargs)

Bases: BusdayLocator

Business-day-aware wrapper around matplotlib.dates.SecondLocator.

Places ticks at the specified seconds, then discards any that fall outside business days or business hours. Midnight ticks are not kept by default because second-level ticks are sub-daily.

All keyword arguments are forwarded to ~matplotlib.dates.SecondLocator.

WeekdayLocator

WeekdayLocator(keep_midnight_ticks=None, **kwargs)

Bases: BusdayLocator

Business-day-aware wrapper around matplotlib.dates.WeekdayLocator.

Places ticks on specified weekdays, then discards any that fall on non-business days (e.g. public holidays). Midnight ticks are always kept because weekday ticks are placed at day boundaries.

All keyword arguments are forwarded to ~matplotlib.dates.WeekdayLocator.

Utilities

mark_gaps

mark_gaps(ax, style='vline', **kwargs)

Mark session-boundary gaps on a "busday"-scale axis.

Draws a visual indicator at every point where the axis collapses a gap (end of a session, overnight, weekend, holiday). Useful for signalling to the reader that time has been removed.

PARAMETER DESCRIPTION
ax

Axis that must already use the "busday" scale.

TYPE: matplotlib.axes.Axes

style

Visual style:

  • "vline" — a thin vertical line at each seam.
  • "broken" — two diagonal slash marks at the top and bottom of the axes at each seam (traditional broken-axis convention).
  • "both" — vline and broken marks combined.

TYPE: ('vline', 'broken', 'both') DEFAULT: "vline"

**kwargs

Forwarded to the underlying artists. Useful keys:

  • color (default "gray" for vline, "k" for broken)
  • linewidth (default 0.8 for vline, 1.5 for broken)
  • linestyle — vline only (default "--")
  • alpha
  • zorder
  • size — broken only, size of the slash marks in points (default 10)

DEFAULT: {}

RETURNS DESCRIPTION
list of matplotlib artists

All artists added to the axes, so the caller can adjust them later.

Examples:

ax.set_xscale("busday", bushours=(9, 17))
busdayaxis.mark_gaps(ax)                          # thin dashed vlines
busdayaxis.mark_gaps(ax, style="broken")          # slash marks only
busdayaxis.mark_gaps(ax, style="both", color="steelblue", alpha=0.4)

holidays_from_exchange

holidays_from_exchange(calendar, start, end)

Return non-trading weekdays as ISO date strings for use with holidays=.

Extracts trading days from an exchange_calendars or pandas_market_calendars calendar object and returns all weekdays in [start, end] that are not trading days — i.e. the holidays and irregular closures you can pass directly to holidays=.

The function duck-types the calendar object and tries all known calling conventions, so no hard dependency on either library is introduced: pandas_market_calendars uses schedule(start_date=, end_date=), while exchange_calendars exposes sessions_in_range(start, end).

PARAMETER DESCRIPTION
calendar

Any object that exposes either a schedule() method returning a DataFrame (pandas_market_calendars style) or a sessions_in_range() method returning a DatetimeIndex (exchange_calendars style).

TYPE: exchange_calendars or pandas_market_calendars calendar

start

Inclusive date range to scan (e.g. "2024-01-01").

TYPE: str or datetime - like

end

Inclusive date range to scan (e.g. "2024-01-01").

TYPE: str or datetime - like

RETURNS DESCRIPTION
list[str]

ISO date strings ("YYYY-MM-DD") of weekdays in [start, end] that are not trading sessions.

Examples:

With exchange_calendars:

import exchange_calendars as xcals
import busdayaxis

cal = xcals.get_calendar("XNYS")
holidays = busdayaxis.holidays_from_exchange(cal, "2025-01-01", "2025-12-31")
ax.set_xscale("busday", holidays=holidays)

With pandas_market_calendars:

import pandas_market_calendars as mcal
import busdayaxis

cal = mcal.get_calendar("NYSE")
holidays = busdayaxis.holidays_from_exchange(cal, "2025-01-01", "2025-12-31")
ax.set_xscale("busday", holidays=holidays)