Holidays and Day Info
Pyplanner separates the calendar structure from the holiday data. Any class that implements the right interface can supply per-day metadata (public holidays, transferred workdays, etc.) to the calendar model.
DayInfo
DayInfo is a simple dataclass that carries metadata about a
single day:
from pyplanner import DayInfo
info = DayInfo(
is_off_day=True,
name="Christmas Day",
local_name="Boze Narodzenie",
launch_year=None,
holiday_types=("Public",),
)
Every field defaults to None, meaning “no data - fall back to default
logic”. When is_off_day is None, the calendar uses the weekday’s default
weekend rule instead.
Built-in providers
Pyplanner ships two providers that fetch holiday data from free public APIs.
NagerDateProvider
Covers 100+ countries via the Nager.Date API. Returns public holidays with names and types. Free, no API key required.
from pyplanner import Calendar
from pyplanner.providers import NagerDateProvider
provider = NagerDateProvider("PL")
cal = Calendar(firstweekday=0, provider=provider)
year = cal.year(2026)
for day in year.days():
if day.name:
print(f"{day.id} {day.name}")
IsDayOffProvider
Covers Russia, Belarus, Kazakhstan, Uzbekistan and Georgia via the isdayoff.ru API. Returns a binary workday/off-day flag for every day of the year including transferred workdays. Free, no API key required.
from pyplanner.providers import IsDayOffProvider
provider = IsDayOffProvider("RU")
cal = Calendar(firstweekday=0, provider=provider)
year = cal.year(2026)
workdays = sum(1 for d in year.days() if not d.is_off_day)
print(f"Workdays in 2026 (Russia): {workdays}")
Both providers issue a warning and return None when the network request
fails. The calendar then falls back to weekday-based weekend rules - no
holidays, but no crash either.
Writing a custom provider
A provider is any class with two methods:
__init__(self, country_code: str)- raiseValueErrorif the country is not supported.fetch_day_info(self, year: int) -> dict[str, DayInfo] | None- return amapping of
YYYY-MM-DDstrings toDayInfoinstances, orNoneon failure.
The provider does not need to inherit from
DayInfoProvider. Pyplanner discovers providers by duck
typing at runtime: if it has fetch_day_info, it qualifies.
Duck typing was chosen over a formal plugin registry (entry points, abstract base class enforcement) because:
It allows standalone single-file providers with zero dependencies on pyplanner.
It avoids the complexity of setuptools entry points for a project that does not publish to PyPI yet.
Duck typing is idiomatic Python and easy to understand.
Here is a minimal standalone provider (e.g. my_holidays.py):
from dataclasses import dataclass
@dataclass
class DayInfo:
is_off_day: bool | None = None
name: str | None = None
local_name: str | None = None
launch_year: int | None = None
holiday_types: tuple[str, ...] | None = None
class CompanyHolidayProvider:
"""Mark company-specific days off."""
def __init__(self, country_code: str) -> None:
if country_code.upper() != "US":
raise ValueError(f"Unsupported: {country_code!r}")
def fetch_day_info(self, year: int):
return {
f"{year}-07-04": DayInfo(
is_off_day=True,
name="Independence Day",
),
f"{year}-12-25": DayInfo(
is_off_day=True,
name="Christmas Day",
),
}
Use it from the CLI:
pyplanner planners/demo --provider my_holidays --country us
Or from code:
from pyplanner import Calendar
from my_holidays import CompanyHolidayProvider
cal = Calendar(
firstweekday=0,
provider=CompanyHolidayProvider("US")
)
Loading providers dynamically
DayInfoProvider.load() imports a module
by dotted name (or file path) and returns every provider class found in it. This
is how the CLI --provider flag works:
from pyplanner import DayInfoProvider
classes = DayInfoProvider.load("pyplanner.providers")
for cls in classes:
print(cls.__name__)
# IsDayOffProvider
# NagerDateProvider
# Load from a file path:
classes = DayInfoProvider.load("my_holidays.py")
API reference
- class pyplanner.DayInfo(is_off_day: bool | None = None, name: str | None = None, local_name: str | None = None, launch_year: int | None = None, holiday_types: tuple[str, ...] | None = None)
Supplementary information about a calendar day.
Every field defaults to
None(no data - fall back to default calendar logic). New fields can be added as the framework grows.holiday_typesvalues (Nager.Date definitions):"Public"- general public holiday, a day off for the population."Bank"- bank holiday; banks and offices are closed."School"- school holiday; schools are closed."Authorities"- government offices are closed."Optional"- majority of people take a day off but it is not mandatory."Observance"- optional festivity, no paid day off.
- class pyplanner.DayInfoProvider(country_code: str)
Interface for day-information providers.
Custom providers: any importable Python module that contains one or more classes with a
fetch_day_info(self, year)method can be loaded viaload(). Plugin modules do not need to depend on pyplanner; classes are discovered at runtime by duck typing.Example standalone plugin (e.g.
my_holidays.py):from dataclasses import dataclass @dataclass class DayInfo: is_off_day: bool | None = None name: str | None = None local_name: str | None = None launch_year: int | None = None holiday_types: tuple[str, ...] | None = None class MyHolidayProvider: def __init__(self, country_code: str) -> None: if country_code.upper() != "US": raise ValueError(f"Unsupported: {country_code!r}") def fetch_day_info(self, year: int): return { f"{year}-12-25": DayInfo( is_off_day=True, name="Christmas Day", ), f"{year}-01-01": DayInfo( is_off_day=True, name="New Year's Day", ), }
Usage:
pyplanner planner.html --provider my_holidays --country us
- static load(module_name: str) list[type[DayInfoProvider]]
Import module_name and return every provider class found in it.
Provider classes are discovered by duck typing - they do not have to inherit from this class. If the name cannot be imported as an installed package, the method falls back to loading a file from disk (see
_load_from_file()).- Parameters:
module_name – Dotted module name or a file path (with or without extension).
- Returns:
List of provider classes found in the module.
- Raises:
TypeError – If the module contains no provider classes.
ModuleNotFoundError – If module_name cannot be imported and no matching file is found on disk.
ImportError – If a matching file is found but cannot be loaded.
- abstractmethod fetch_day_info(year: int) dict[str, DayInfo] | None
Return day information keyed by date id (
YYYY-MM-DD).- Parameters:
year – Calendar year to fetch data for.
- Returns:
Mapping of
YYYY-MM-DDstrings toDayInfoinstances, orNonewhen the data source is unreachable or the response is unusable. Missing keys are treated as “no extra info” (equivalent toDayInfo()).
- class pyplanner.providers.NagerDateProvider(country_code: str, *, timeout: float = 10)
DayInfoProvider backed by the Nager.Date public-holiday API.
Covers 100+ countries (ISO 3166-1 alpha-2 codes). Free, no API key required. https://date.nager.at
- class pyplanner.providers.IsDayOffProvider(country_code: str, *, timeout: float = 10)
DayInfoProvider backed by the isdayoff.ru production-calendar API.
Provides complete workday/off-day data including public holidays and transferred workdays. Free, no API key required.
Supported countries: RU, BY, KZ, UZ, GE.