mirror of
https://git.pumpin.online/funkpower/typikon.git
synced 2026-10-23 03:07:12 +00:00
rebuild the engine in Rust
Python was never the house language here: the mw-* desktop surface it came
from is 39 bash scripts and was exactly one Python script, the one this was
extracted from. What it cost was distribution, a widget dependency that
needed pipx and a Python floor, and 24ms of interpreter startup before a line
of the program ran.
The port is verified rather than asserted. Both implementations were run over
199,446 day-records, 26 jurisdictions across 21 years, with every field of
every record required to match; the JSON, the iCalendar and 480 renderings of
the text output are identical too. examples/dump.rs is the harness.
What the type system actually buys, which is less than the usual claim and
more than nothing:
- Level is an ordered enum, strictest first, so reads as 'a is
stricter' and relaxed_to() is the one place a feast can lift a fast. No
rule can get the direction backwards.
- Date is a newtype over the Julian Day Number and JulianDate is a separate
type, so a civil date and an ecclesiastical one cannot be mixed up. That
confusion is the single most common way calendar code goes wrong.
- Octets is a newtype in the iCalendar folder, because the bug it replaced
counted characters against a limit RFC 5545 states in octets.
- Movables is a struct, not a map, so a mistyped key is a compile error
rather than a None at the far end of the program.
Of the six defects an adversarial review found in the Python, exactly one had
a shape a type system would have caught. The rest were logic, and the tests
came with them.
The menologion is compiled in with include_str!, so the binary answers on its
own: 930KB, no runtime, 1.2ms to start against Python's 36ms.
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
//! Who keeps which calendar, and by whose rule.
|
||||
//!
|
||||
//! A jurisdiction is four independent choices, and conflating any two of them
|
||||
//! is how calendar code usually goes wrong:
|
||||
//!
|
||||
//! - **paschalion**: which table gives Pascha
|
||||
//! - **menologion**: which calendar the fixed feasts are read on
|
||||
//! - **rule**: which fasting discipline is kept
|
||||
//! - **menaia**: which supplementary commemorations to load, if any
|
||||
//!
|
||||
//! The pairings are not free. Almost every Orthodox Church keeps the Julian
|
||||
//! Paschalion; what divides them is the menologion. The "old calendar" churches
|
||||
//! read the fixed feasts on the Julian calendar too, the "new calendar"
|
||||
//! churches on the Revised Julian, which agrees with the Gregorian for every
|
||||
//! date until 2800. So a new-calendar church keeps the Nativity with the West
|
||||
//! and Pascha with the East, which is not a compromise but exactly what the
|
||||
//! 1923 congress decided.
|
||||
//!
|
||||
//! Only two bodies here use the Gregorian Paschalion: the Finnish Orthodox
|
||||
//! Church, alone among the Orthodox, and the Byzantine Catholic churches.
|
||||
|
||||
use crate::calendars::Date;
|
||||
use crate::paschalion::Paschalion;
|
||||
|
||||
/// Which calendar the *fixed* feasts are read on.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum MenologionKind {
|
||||
Julian,
|
||||
/// Revised Julian, which agrees with the Gregorian for every date until 2800.
|
||||
RevisedJulian,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum RuleKind {
|
||||
Orthodox,
|
||||
Ruthenian,
|
||||
}
|
||||
|
||||
/// The short name people use for a paschalion/menologion combination.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum Calendar {
|
||||
Old,
|
||||
New,
|
||||
Western,
|
||||
}
|
||||
|
||||
impl Calendar {
|
||||
pub fn key(self) -> &'static str {
|
||||
match self {
|
||||
Calendar::Old => "old",
|
||||
Calendar::New => "new",
|
||||
Calendar::Western => "western",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Jurisdiction {
|
||||
pub key: &'static str,
|
||||
pub name: &'static str,
|
||||
pub paschalion: Paschalion,
|
||||
pub menologion: MenologionKind,
|
||||
pub rule: RuleKind,
|
||||
/// Supplementary commemoration sets to overlay, fetched not shipped.
|
||||
pub menaia: &'static [&'static str],
|
||||
pub note: &'static str,
|
||||
}
|
||||
|
||||
impl Jurisdiction {
|
||||
pub fn calendar(&self) -> Calendar {
|
||||
match (self.paschalion, self.menologion) {
|
||||
(Paschalion::Gregorian, MenologionKind::RevisedJulian) => Calendar::Western,
|
||||
(_, MenologionKind::Julian) => Calendar::Old,
|
||||
_ => Calendar::New,
|
||||
}
|
||||
}
|
||||
|
||||
/// The ecclesiastical (year, month, day) a civil date is reckoned as.
|
||||
pub fn fixed_of_civil(&self, date: Date) -> (i32, u32, u32) {
|
||||
match self.menologion {
|
||||
MenologionKind::Julian => {
|
||||
let j = date.to_julian();
|
||||
(j.year, j.month, j.day)
|
||||
}
|
||||
MenologionKind::RevisedJulian => date.ymd(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The civil date of an ecclesiastical date in a given church year.
|
||||
pub fn civil_of_fixed(&self, year: i32, month: u32, day: u32) -> Date {
|
||||
match self.menologion {
|
||||
MenologionKind::Julian => {
|
||||
crate::calendars::JulianDate { year, month, day }.to_civil()
|
||||
}
|
||||
MenologionKind::RevisedJulian => Date::from_ymd(year, month, day)
|
||||
.unwrap_or_else(|| Date::from_ymd(year, month, 28).expect("28th exists")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use MenologionKind::{Julian as JulMen, RevisedJulian as RevJul};
|
||||
use Paschalion::{Gregorian as GregP, Julian as JulP};
|
||||
use RuleKind::{Orthodox as Orth, Ruthenian as Ruth};
|
||||
|
||||
const NO_MENAIA: &[&str] = &[];
|
||||
const RUTHENIAN_MENAIA: &[&str] = &["ruthenian"];
|
||||
|
||||
/// A reckoning without naming a church. `new` is the default.
|
||||
pub const CALENDARS: &[Jurisdiction] = &[
|
||||
Jurisdiction { key: "old", name: "Old Calendar", paschalion: JulP, menologion: JulMen, rule: Orth, menaia: NO_MENAIA,
|
||||
note: "Julian throughout, the reckoning of Moscow, Serbia, Georgia, Jerusalem and the Holy Mountain." },
|
||||
Jurisdiction { key: "new", name: "New Calendar", paschalion: JulP, menologion: RevJul, rule: Orth, menaia: NO_MENAIA,
|
||||
note: "Revised Julian fixed feasts with the Julian Paschalion: Constantinople, Greece, Antioch, Romania, Bulgaria, the OCA." },
|
||||
Jurisdiction { key: "western", name: "Gregorian", paschalion: GregP, menologion: RevJul, rule: Orth, menaia: NO_MENAIA,
|
||||
note: "Gregorian throughout, Pascha included. Kept by the Finnish Orthodox Church and by most Byzantine Catholics." },
|
||||
];
|
||||
|
||||
pub const JURISDICTIONS: &[Jurisdiction] = &[
|
||||
// old calendar
|
||||
Jurisdiction { key: "moscow", name: "Russian Orthodox Church", paschalion: JulP, menologion: JulMen, rule: Orth, menaia: NO_MENAIA, note: "" },
|
||||
Jurisdiction { key: "rocor", name: "Russian Orthodox Church Outside Russia", paschalion: JulP, menologion: JulMen, rule: Orth, menaia: NO_MENAIA, note: "" },
|
||||
Jurisdiction { key: "serbian", name: "Serbian Orthodox Church", paschalion: JulP, menologion: JulMen, rule: Orth, menaia: NO_MENAIA, note: "" },
|
||||
Jurisdiction { key: "georgian", name: "Georgian Orthodox Church", paschalion: JulP, menologion: JulMen, rule: Orth, menaia: NO_MENAIA, note: "" },
|
||||
Jurisdiction { key: "jerusalem", name: "Patriarchate of Jerusalem", paschalion: JulP, menologion: JulMen, rule: Orth, menaia: NO_MENAIA, note: "" },
|
||||
Jurisdiction { key: "athos", name: "The Holy Mountain", paschalion: JulP, menologion: JulMen, rule: Orth, menaia: NO_MENAIA,
|
||||
note: "Athonite practice is stricter than the printed Typikon at many points." },
|
||||
Jurisdiction { key: "polish", name: "Polish Orthodox Church", paschalion: JulP, menologion: JulMen, rule: Orth, menaia: NO_MENAIA,
|
||||
note: "Returned to the Julian menologion in 2014 after decades on the Revised Julian; some parishes kept the new calendar." },
|
||||
Jurisdiction { key: "macedonian", name: "Macedonian Orthodox Church", paschalion: JulP, menologion: JulMen, rule: Orth, menaia: NO_MENAIA, note: "" },
|
||||
|
||||
// new calendar
|
||||
Jurisdiction { key: "constantinople", name: "Ecumenical Patriarchate", paschalion: JulP, menologion: RevJul, rule: Orth, menaia: NO_MENAIA, note: "" },
|
||||
Jurisdiction { key: "goarch", name: "Greek Orthodox Archdiocese of America", paschalion: JulP, menologion: RevJul, rule: Orth, menaia: NO_MENAIA, note: "" },
|
||||
Jurisdiction { key: "greece", name: "Church of Greece", paschalion: JulP, menologion: RevJul, rule: Orth, menaia: NO_MENAIA, note: "" },
|
||||
Jurisdiction { key: "antiochian", name: "Antiochian Orthodox Church", paschalion: JulP, menologion: RevJul, rule: Orth, menaia: NO_MENAIA, note: "" },
|
||||
Jurisdiction { key: "oca", name: "Orthodox Church in America", paschalion: JulP, menologion: RevJul, rule: Orth, menaia: NO_MENAIA,
|
||||
note: "Most parishes keep the new calendar; a minority, chiefly of Carpatho-Russian and Alaskan background, keep the old." },
|
||||
Jurisdiction { key: "romanian", name: "Romanian Orthodox Church", paschalion: JulP, menologion: RevJul, rule: Orth, menaia: NO_MENAIA, note: "" },
|
||||
Jurisdiction { key: "bulgarian", name: "Bulgarian Orthodox Church", paschalion: JulP, menologion: RevJul, rule: Orth, menaia: NO_MENAIA, note: "" },
|
||||
Jurisdiction { key: "cyprus", name: "Church of Cyprus", paschalion: JulP, menologion: RevJul, rule: Orth, menaia: NO_MENAIA, note: "" },
|
||||
Jurisdiction { key: "albanian", name: "Orthodox Church of Albania", paschalion: JulP, menologion: RevJul, rule: Orth, menaia: NO_MENAIA, note: "" },
|
||||
Jurisdiction { key: "ukrainian", name: "Orthodox Church of Ukraine", paschalion: JulP, menologion: RevJul, rule: Orth, menaia: NO_MENAIA,
|
||||
note: "Moved the fixed feasts to the Revised Julian calendar on 1 September 2023. The Julian Paschalion is retained." },
|
||||
|
||||
// gregorian throughout
|
||||
Jurisdiction { key: "finnish", name: "Finnish Orthodox Church", paschalion: GregP, menologion: RevJul, rule: Orth, menaia: NO_MENAIA,
|
||||
note: "The only Orthodox Church keeping the Gregorian Paschalion." },
|
||||
|
||||
// eastern catholic
|
||||
Jurisdiction { key: "ruthenian", name: "Byzantine Catholic Metropolia of Pittsburgh", paschalion: GregP, menologion: RevJul, rule: Ruth, menaia: RUTHENIAN_MENAIA,
|
||||
note: "Gregorian throughout. Fasting is reported under Ruthenian particular law, which binds far less than the Typikon prescribes." },
|
||||
Jurisdiction { key: "ugcc", name: "Ukrainian Greek Catholic Church", paschalion: JulP, menologion: RevJul, rule: Ruth, menaia: RUTHENIAN_MENAIA,
|
||||
note: "Moved the fixed feasts to the Revised Julian calendar on 1 September 2023, keeping the Julian Paschalion. Eparchies outside Ukraine vary; several keep the Gregorian Paschalion." },
|
||||
Jurisdiction { key: "melkite", name: "Melkite Greek Catholic Church", paschalion: GregP, menologion: RevJul, rule: Ruth, menaia: NO_MENAIA,
|
||||
note: "Practice is not uniform: parishes in the Middle East commonly keep the Julian Paschalion for the sake of the local Orthodox." },
|
||||
Jurisdiction { key: "romanian-catholic", name: "Romanian Greek Catholic Church", paschalion: GregP, menologion: RevJul, rule: Ruth, menaia: NO_MENAIA, note: "" },
|
||||
];
|
||||
|
||||
/// A jurisdiction key, a calendar name, or `None` for the default.
|
||||
pub fn resolve(name: Option<&str>) -> Result<&'static Jurisdiction, String> {
|
||||
let Some(name) = name else {
|
||||
return Ok(&CALENDARS[1]); // new
|
||||
};
|
||||
let key = name.trim().to_ascii_lowercase();
|
||||
JURISDICTIONS
|
||||
.iter()
|
||||
.chain(CALENDARS.iter())
|
||||
.find(|j| j.key == key)
|
||||
.ok_or_else(|| name.to_string())
|
||||
}
|
||||
Reference in New Issue
Block a user