Skip to main content

Liturgical calendar library for calculating Catholic liturgical dates and calendars

Project description

Romcal

A Python library for calculating Catholic liturgical dates and generating liturgical calendars. Powered by Rust via UniFFI bindings.

For the Rust library, see romcal. For command-line usage, see the CLI documentation.

Installation

pip install romcal

Or with uv:

uv add romcal

Note: Pre-built wheels are available for Python 3.11-3.14 on Linux (x86_64, aarch64, musl), macOS (Intel, Apple Silicon), and Windows (x64, ARM64). For other platforms, Rust is required to build from source.

Quick Start

from romcal import Romcal

# Create a default instance
romcal = Romcal()

# Get a specific liturgical date
easter = romcal.get_date("easter_sunday", 2026)
print(easter)  # "2026-04-05"

# Generate the liturgical calendar for year 2026
calendar = romcal.liturgical_calendar(2026)

# Access a specific date
christmas = calendar.get("2026-12-25")
if christmas:
    print(christmas[0]["fullname"])  # "The Nativity of the Lord"

Configuration

Using Keyword Arguments

from romcal import CalendarContext, Romcal

# With calendar and locale
romcal1 = Romcal(calendar="france", locale="fr")

# With full configuration
romcal2 = Romcal(
    calendar="france",
    locale="fr",
    context=CalendarContext.LITURGICAL,
    epiphany_on_sunday=True,
    ascension_on_sunday=True,
    corpus_christi_on_sunday=True,
)

Configuration Options

Option Type Default Description
calendar str "general_roman" Calendar ID (e.g., "france", "united_states")
locale str "en" Locale code (e.g., "fr", "es")
context CalendarContext GREGORIAN GREGORIAN (Jan-Dec) or LITURGICAL (Advent-Advent)
epiphany_on_sunday bool False Celebrate Epiphany on Sunday (Jan 2-8) instead of Jan 6
ascension_on_sunday bool False Celebrate Ascension on Sunday instead of Thursday
corpus_christi_on_sunday bool True Celebrate Corpus Christi on Sunday instead of Thursday
easter_calculation_type EasterCalculationType GREGORIAN GREGORIAN or JULIAN Easter calculation
calendar_definitions_json str None JSON string of calendar definitions
resources_json str None JSON string of locale resources

Loading Calendar Data

Without loading data, only the Proper of Time is available. To include the General Roman Calendar, particular calendars, and localized names, load calendar definitions and resources:

import json
from pathlib import Path
from romcal import Romcal

DATA_DIR = Path("data")

def load_calendar_definitions():
    """Load all calendar definitions from the data folder."""
    definitions = []
    for json_file in (DATA_DIR / "definitions").rglob("*.json"):
        with open(json_file) as f:
            definitions.append(json.load(f))
    return definitions

def load_resources():
    """Load all resources from the data folder."""
    resources_dir = DATA_DIR / "resources"
    resources = []

    # Group files by locale
    files_by_locale = {}
    for json_file in resources_dir.rglob("*.json"):
        locale = json_file.parent.name
        files_by_locale.setdefault(locale, []).append(json_file)

    # Merge files for each locale
    for locale, locale_files in files_by_locale.items():
        metadata = None
        entities = {}

        for file in locale_files:
            with open(file) as f:
                content = json.load(f)
            if file.name == "meta.json":
                metadata = content.get("metadata")
            elif file.name.startswith("entities.") and "entities" in content:
                entities.update(content["entities"])

        resources.append({
            "locale": locale,
            "metadata": metadata,
            "entities": entities if entities else None,
        })

    return resources

# Create instance with loaded data
romcal = Romcal(
    calendar="france",
    locale="fr",
    calendar_definitions_json=json.dumps(load_calendar_definitions()),
    resources_json=json.dumps(load_resources()),
)

API

Romcal()

Creates a new Romcal instance.

from romcal import Romcal

# Default configuration
romcal1 = Romcal()

# With calendar and locale
romcal2 = Romcal(calendar="france", locale="fr")

# With partial configuration
romcal3 = Romcal(
    calendar="france",
    locale="fr",
    epiphany_on_sunday=True,
)

Romcal Instance

liturgical_calendar(year)

Generate the complete liturgical calendar for a given year.

calendar = romcal.liturgical_calendar(2026)
# calendar is dict[str, list[dict]]
# Keys are dates in "YYYY-MM-DD" format

for date, days in calendar.items():
    for day in days:
        print(f"{date}: {day['fullname']} ({day['rank']})")

mass_calendar(year)

Generate a mass-centric view of the calendar organized by civil date and mass time.

mass_calendar = romcal.mass_calendar(2026)
# mass_calendar is dict[str, list[dict]]

# Evening masses appear on the previous civil day
easter_vigil_day = mass_calendar.get("2026-04-04")
if easter_vigil_day:
    vigil = next((m for m in easter_vigil_day if m["mass_time"] == "EASTER_VIGIL"), None)
    if vigil:
        print(vigil["liturgical_date"])  # "2026-04-05"

get_date(id, year)

Get a liturgical date by its ID.

easter = romcal.get_date("easter_sunday", 2026)      # "2026-04-05"
ash_wed = romcal.get_date("ash_wednesday", 2026)     # "2026-02-18"
pentecost = romcal.get_date("pentecost_sunday", 2026) # "2026-05-24"
christmas = romcal.get_date("christmas", 2026)        # "2026-12-25"

Any date ID from the liturgical calendar can be used (e.g., easter_sunday, christmas, ordinary_time_5_monday).

Properties

Access the resolved configuration:

print(romcal.calendar)                # "france"
print(romcal.locale)                  # "fr"
print(romcal.epiphany_on_sunday)      # True
print(romcal.ascension_on_sunday)     # False
print(romcal.corpus_christi_on_sunday) # True
print(romcal.easter_calculation_type)  # "GREGORIAN"
print(romcal.context)                  # "GREGORIAN"

Key Types

For detailed documentation on liturgical types (seasons, ranks, precedence, colors, cycles, mass times), see the romcal documentation.

Error Handling

All operations may raise RomcalError:

from romcal import Romcal, RomcalError

try:
    romcal = Romcal()
    # Year must be >= 1583 (Gregorian calendar adoption)
    calendar = romcal.liturgical_calendar(1500)
except RomcalError as e:
    print(f"Romcal error: {e}")

Development

Requirements

  • Python 3.11 or later
  • uv (recommended) or pip
  • Rust 1.85 or later

Setup

cd bindings/python

# Create virtual environment
uv venv

# Install build tools
uv pip install maturin uniffi-bindgen

# Build and install the native extension
uv run maturin develop

# Install dev dependencies (pytest, ruff, etc.)
uv pip install pytest taskipy ruff mypy

Available Tasks

Using taskipy:

task build          # maturin build --release
task develop        # maturin develop
task generate-types # Generate Pydantic types from JSON schema
task test           # pytest tests/ -v
task test-run       # pytest tests/ (without verbose)
task format         # ruff format .
task format-check   # ruff format --check .
task lint           # ruff check .
task lint-fix       # ruff check --fix .
task typecheck      # mypy src/

Testing

task test      # Run tests with verbose output
task test-run  # Run tests once

Project Structure

bindings/python/
├── src/
│   └── romcal/
│       ├── __init__.py    # Main entry point, API wrapper
│       ├── types.py       # Generated types from JSON schema (Pydantic)
│       └── _uniffi/       # Generated UniFFI bindings
├── tests/
│   ├── conftest.py        # Pytest fixtures (data loading)
│   ├── test_config.py     # Configuration tests
│   ├── test_calendar.py   # Calendar generation tests
│   └── test_data_loading.py # Data loading tests
├── examples/
│   └── basic_usage.py     # Usage example with data loading
└── pyproject.toml         # Project configuration

Running Examples

# Basic usage example (loads data from /data folder)
python examples/basic_usage.py

Regenerating Types

If you modify Rust types in core/src/, you need to regenerate types.py:

uv run task generate-types

This uses datamodel-codegen to generate Pydantic models from JSON schema.

Related

License

Apache License 2.0. See LICENSE for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

romcal-4.0.0b4-py3-none-musllinux_1_2_x86_64.whl (1.2 MB view details)

Uploaded Python 3musllinux: musl 1.2+ x86-64

romcal-4.0.0b4-py3-none-musllinux_1_2_aarch64.whl (1.2 MB view details)

Uploaded Python 3musllinux: musl 1.2+ ARM64

romcal-4.0.0b4-py3-none-manylinux_2_24_x86_64.whl (997.8 kB view details)

Uploaded Python 3manylinux: glibc 2.24+ x86-64

romcal-4.0.0b4-py3-none-manylinux_2_24_aarch64.whl (986.9 kB view details)

Uploaded Python 3manylinux: glibc 2.24+ ARM64

romcal-4.0.0b4-py3-none-macosx_11_0_arm64.whl (909.9 kB view details)

Uploaded Python 3macOS 11.0+ ARM64

romcal-4.0.0b4-py3-none-macosx_10_12_x86_64.whl (925.3 kB view details)

Uploaded Python 3macOS 10.12+ x86-64

File details

Details for the file romcal-4.0.0b4-py3-none-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for romcal-4.0.0b4-py3-none-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 211f001b83244a8858698013398b61aef6d1b0a5bf530b85ecfa04f2787c1323
MD5 08f7b2b704d7dfeaa0049041cef4d041
BLAKE2b-256 6018dfa88ee00a66115f7b4c612992870fd8792cd86ac2eb8a6eb684f9256211

See more details on using hashes here.

File details

Details for the file romcal-4.0.0b4-py3-none-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for romcal-4.0.0b4-py3-none-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 811fd92220a6638900ffabc9fa954379251b2e5302b5a68a2cb1927d163a139d
MD5 74a0b3be9c491b2325ee03cb4ff08533
BLAKE2b-256 d91d8d7476f8e1cdeec21e62ec9c8426c90c42300e3164a3024fd834083a21d7

See more details on using hashes here.

File details

Details for the file romcal-4.0.0b4-py3-none-manylinux_2_24_x86_64.whl.

File metadata

File hashes

Hashes for romcal-4.0.0b4-py3-none-manylinux_2_24_x86_64.whl
Algorithm Hash digest
SHA256 766706c1c6bdbae1f23724a053b2ba9e4dcd2f25a1d33a5a719fa4512358a05e
MD5 a6554e27e82b32bfdcf7a549e2885058
BLAKE2b-256 9c50e32af67edf1706a24f6f78aaea3e05924594ed036b6b5184873908751103

See more details on using hashes here.

File details

Details for the file romcal-4.0.0b4-py3-none-manylinux_2_24_aarch64.whl.

File metadata

File hashes

Hashes for romcal-4.0.0b4-py3-none-manylinux_2_24_aarch64.whl
Algorithm Hash digest
SHA256 4f9c591bee82b951421423d375cbecabbbf1e5ec41ccf68431d5517cde239aad
MD5 feae07dbd94b6d46ed994ad6eb28f2de
BLAKE2b-256 a20ff76b8ba445f4c4a985f736c925d9636441f1c1669abf1dd7386616a0acc0

See more details on using hashes here.

File details

Details for the file romcal-4.0.0b4-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for romcal-4.0.0b4-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 80026aa2f8c4e24e5612cf118e3571d898d27ac6ac8d6008e690cf95d24ad4e7
MD5 93b88fe6d751cea84e7728848de395c1
BLAKE2b-256 a1a1934b2984304ca994814fc9484a5bb7073159bf98758e3b7840550b3d5a06

See more details on using hashes here.

File details

Details for the file romcal-4.0.0b4-py3-none-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for romcal-4.0.0b4-py3-none-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 a1051d6bc88bbc9b1e1b128c7912845b5083832e96558213437af326d3a91f6e
MD5 3b520f335ffde2d4e6ecc16ad1df32f1
BLAKE2b-256 7846473a4011a4144be7a8fe8bbb8a79fc7bfdfccd6953463fe43fb56e529f1f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page