The engine is the same; only the front door is new. pyo3 behind an optional
feature, maturin for the wheel, and the bindings mirror DayJson rather than Day
because DayJson is already the shape front ends read and the one that has to stay
still. to_dict() therefore gives exactly what --json gives, and a test holds the
two to that.
That test earned its place immediately. The bindings were calling
jurisdictions::resolve(None), which answers with a built-in default that knows
nothing about the machine, so Python was reckoning by the New Calendar while the
command line on the same box reckoned by ROCOR — every field downstream of the
calendar differed, including the fast. They now take the same two steps main.rs
takes, and agree field for field on Pascha, the Nativity and Theophany.
The wheel is abi3, so one build serves 3.9 upward rather than one per
interpreter. That costs the datetime C API, which the limited API does not
expose, so a date is read through its attributes instead — which incidentally
makes datetime.datetime work as well as datetime.date. Constructing dates is
unaffected, pyo3 calls Python's own constructor for that.
panic = "abort" comes out of the release profile. An extension module has to be
able to unwind a panic into a Python exception; aborting would take the
interpreter down with it. The CLI keeps the same behaviour it always had, a
panic there just unwinds now.
Mixed layout so the wheel can carry py.typed and a stub file, which a bare
extension module has nowhere to put. mypy --strict is clean against the stubs
and catches a deliberate error, so the Typing :: Typed classifier is not a claim
about nothing.
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.
Feasts, seasons and the fast for twenty-three jurisdictions, derived from
the paschalion and the fixed calendar so it needs no network and cannot go
stale.
The three things calendar code usually conflates are kept apart: the
paschalion that gives Pascha, the menologion the fixed feasts are read on,
and the fasting rule that is kept. Almost every Orthodox Church keeps the
Julian Paschalion; what divides them is the menologion, so one file of
commemorations indexed by ecclesiastical date serves old and new calendars
alike.
Six fasting levels rather than three, because the difference between
xerophagy and wine-and-oil is the difference the books actually make. Level
and obligation are carried separately: the Orthodox rule reports everything
as customary, and Ruthenian particular law reports both how a day is kept
and the far smaller minimum that binds.
The menologion covers all 366 days and is dedicated to the public domain.
Sets published by someone else are fetched, never redistributed.