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.daysA flat list of
Dayobjects, one per calendar day (1st through 28th/29th/30th/31st).month.tableA week-aligned grid where each row is a 7-element list. Cells contain a
DayorNonefor 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
Yearobjects from the stdlibcalendarmodule, optionally enriched with holiday data.- Parameters:
firstweekday – First day of the week (0 = Monday … 6 = Sunday).
provider – Optional
DayInfoProviderfor holidays.lang – Language code for month/weekday names (default
"en").country – ISO 3166-1 alpha-2 code used to determine default weekend days.
- 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
Monthobjects.id – Year identifier (
YYYYstring).
- 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
Dayobjects for every day in the month.table – Week-aligned grid. Each row is a 7-element list where cells are either a
DayorNone(padding for days outside the month).id – Month identifier in
YYYY-MMformat.
- 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:
Templates typically use
day.value,day.weekday,day.is_off_dayandday.id. Theis_off_dayproperty 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
calendarweekday constant (0 = Monday … 6 = Sunday). Falls back to0(Monday) when the country is unknown.
- static create(day: int, country: str | None = None, lang: str | None = None) WeekDay
Create a
WeekDayfor 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 forGB).- 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-6where0= Monday and6= 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.