Calendar and Days

The calendar model is the core data structure that planner templates consume. It is built around four classes - Calendar, Year, Month and Day - plus WeekDay for weekday metadata and Lang for localization.

Building a calendar

Create a Calendar and call its year() method:

from pyplanner import Calendar

cal = Calendar()           # Monday start, English names
year = cal.year(2026)

print(year)                # 2026
print(year.isleap)         # False
print(len(year.months))    # 12

The firstweekday parameter controls which day starts the week (0 = Monday, 6 = Sunday). It affects the week-aligned table layout in every month:

cal_us = Calendar(firstweekday=6)   # Sunday start
cal_sa = Calendar(firstweekday=5)   # Saturday start

Months and days

Each Year contains twelve Month objects:

january = year.months[0]

print(january.name)        # January
print(january.short_name)  # Jan
print(january.value)       # 1
print(january.id)          # 2026-01

A month exposes its days in two ways:

month.days

A flat list of Day objects, one per calendar day (1st through 28th/29th/30th/31st).

month.table

A week-aligned grid where each row is a 7-element list. Cells contain a Day or None for padding days outside the month. This is the structure templates use to render calendar grids:

for week in january.table:
    row = []
    for cell in week:
        if cell is None:
            row.append("  ")
        else:
            row.append(f"{cell.value:2d}")
    print(" ".join(row))

Day properties

Each Day carries:

day = january.days[0]

day.value          # 1
day.id             # "2026-01-01"
day.weekday        # WeekDay instance
day.is_off_day     # True if weekend or holiday

# Holiday metadata (populated by a DayInfoProvider):
day.name           # "New Year's Day" or None
day.local_name     # localized holiday name or None
day.launch_year    # year the holiday was established or None
day.holiday_types  # ("Public",) or None

The is_off_day property first checks whether a DayInfoProvider explicitly marked the day, then falls back to the weekday’s default weekend rule.

Iterating all days in a year

Year.days() is a generator that yields every day across all twelve months:

off_count = sum(1 for d in year.days() if d.is_off_day)
print(f"{off_count} off-days in {year}")

Weekdays

WeekDay objects are available on every Day and on the calendar itself:

cal = Calendar(firstweekday=0, country="us")

for wd in cal.weekdays:
    print(wd.name, wd.short_name, wd.letter, wd.is_off_day)

cal.weekdays is rotated so the first element matches firstweekday. Templates use it to render weekday headers in calendar grids.

Country-aware weekdays

When a country is provided, the weekend rule is determined automatically. Most countries use Saturday-Sunday, but some use Friday-Saturday (e.g. AE, SA) or Friday only (MR):

from pyplanner import WeekDay

# What day does the week start on in the US?
print(WeekDay.first_weekday_for_country("us"))  # 6 (Sunday)

# Create a weekday with UAE weekend rules
friday = WeekDay.create(4, country="ae")
print(friday.is_off_day)  # True (Friday is off in UAE)

Localization

Month and weekday names are localized through the Lang registry. Lang is a frozen dataclass with a static registry. Languages are registered at import time by calling Lang.add(). An alias map (ko -> kr) lets users pass either code without duplicating the entire translation dataset.

The registry pattern was chosen over file-based translations (JSON/YAML) because the data is small (7 weekday names + 12 month names per language), a frozen dataclass with __post_init__ validation catches errors at import time rather than at render time, and there is no file I/O or parsing overhead.

Built-in languages are en, ru and kr.

cal_ru = Calendar(lang="ru")
year_ru = cal_ru.year(2026)
print(year_ru.months[0].name)  # Январь

You can register a custom language:

from pyplanner.lang import Lang

Lang.add(Lang(
    code="de",
    weekday_names=(
        "Montag", "Dienstag", "Mittwoch", "Donnerstag",
        "Freitag", "Samstag", "Sonntag",
    ),
    weekday_short_names=("Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"),
    weekday_letters=("M", "D", "M", "D", "F", "S", "S"),
    month_names=(
        "Januar", "Februar", "Maerz", "April", "Mai",
        "Juni", "Juli", "August", "September", "Oktober",
        "November", "Dezember",
    ),
    month_short_names=(
        "Jan", "Feb", "Mae", "Apr", "Mai", "Jun",
        "Jul", "Aug", "Sep", "Okt", "Nov", "Dez",
    ),
))

cal_de = Calendar(lang="de")
print(cal_de.year(2026).months[0].name)  # Januar

API reference

class pyplanner.Calendar(firstweekday: int = 0, provider: DayInfoProvider | None = None, lang: str | None = None, country: str | None = None)

Factory that builds Year objects from the stdlib calendar module, optionally enriched with holiday data.

Parameters:
  • firstweekday – First day of the week (0 = Monday … 6 = Sunday).

  • provider – Optional DayInfoProvider for holidays.

  • lang – Language code for month/weekday names (default "en").

  • country – ISO 3166-1 alpha-2 code used to determine default weekend days.

year(the_year: int) Year

Construct a Year calendar object.

When a provider was supplied at construction time it is queried for supplementary day information (holidays, transferred workdays, etc.).

Parameters:

the_year – Calendar year to build.

Returns:

Fully populated Year instance.

class pyplanner.Year(year: int, months: Iterable[Month], id: str)

A calendar year containing twelve months.

Parameters:
  • year – Four-digit year number.

  • months – Sequence of twelve Month objects.

  • id – Year identifier (YYYY string).

class pyplanner.Month(value: int, name: str, short_name: str, days: Iterable[Day], table: Iterable[Iterable[Day | None]], id: str)

A calendar month containing its days and a week-aligned table.

Parameters:
  • value – Month number (1-12).

  • name – Full localized month name (e.g. "January").

  • short_name – Abbreviated month name (e.g. "Jan").

  • days – Sequence of Day objects for every day in the month.

  • table – Week-aligned grid. Each row is a 7-element list where cells are either a Day or None (padding for days outside the month).

  • id – Month identifier in YYYY-MM format.

class pyplanner.Day(day: int, weekday: WeekDay, id: str, info: DayInfo = DayInfo(is_off_day=None, name=None, local_name=None, launch_year=None, holiday_types=None))

A single calendar day.

Parameters:
  • day – Day-of-month number (1-31).

  • weekdayWeekDay for this day.

  • id – Date identifier in YYYY-MM-DD format.

  • info – Optional DayInfo with additional information about the day. Defaults to an empty DayInfo().

Templates typically use day.value, day.weekday, day.is_off_day and day.id. The is_off_day property checks the provider data first and falls back to the weekday’s default weekend rule.

class pyplanner.WeekDay(day: int, name: str, short_name: str, letter: str, is_off_day: bool)

A weekday with localized names and an off-day flag.

Parameters:
  • day – Weekday number (0 = Monday … 6 = Sunday).

  • name – Full localized name (e.g. "Monday").

  • short_name – Abbreviated name (e.g. "Mon").

  • letter – Single-letter abbreviation (e.g. "M").

  • is_off_day – Whether this weekday is a day off or not.

Use the create() factory to build instances with the correct names and weekend flag for a given country and language.

static first_weekday_for_country(country_code: str) int

Return the conventional first weekday for country_code.

Parameters:

country_code – ISO 3166-1 alpha-2 country code (case-insensitive).

Returns:

Python calendar weekday constant (0 = Monday … 6 = Sunday). Falls back to 0 (Monday) when the country is unknown.

static create(day: int, country: str | None = None, lang: str | None = None) WeekDay

Create a WeekDay for the given weekday number and country.

The country determines which days of the week are considered off days (e.g. Friday-Saturday for AE, Saturday-Sunday for GB).

Parameters:
  • day – Weekday number (0 = Monday … 6 = Sunday).

  • country – ISO 3166-1 alpha-2 country code (case-insensitive). Defaults to Saturday-Sunday weekend when None.

  • lang – Language for weekday names. Default language when None.

static parse_weekday(value: str) int

Parse a weekday given as a name or integer string.

Accepts any supported language names (case-insensitive) or an integer 0-6 where 0 = Monday and 6 = Sunday.

Raises:

ValueError – If value is not a recognized weekday.

class pyplanner.lang.Lang(code: str, weekday_names: tuple[str, ...], weekday_short_names: tuple[str, ...], weekday_letters: tuple[str, ...], month_names: tuple[str, ...], month_short_names: tuple[str, ...])

Translated calendar strings for a single language.

static add(lang: Lang) None

Register a language in the global registry.

static get(code: str | None = None) Lang

Return the registered Lang for code.

When code is None the default language is returned.

Raises:

KeyError – If code is not registered.

static supported() tuple[str, ...]

Return codes of all registered languages.