The years this calendar answers for are now 1 to 9999 and enforced at every
constructor. Arithmetic saturates instead of wrapping, which in a release
build had put a date in antiquity and produced an ISO string that was not ten
characters long; a seven-day walk stops at the boundary rather than running
past it.
--days took any integer and aborted the process trying to allocate for it, as
did --indent. Both are bounded now.
--update-menaion parsed a day out of a fetched page with no bound on it and
wrote keys like 01-4294967295 that no lookup could ever match, then wrote the
file even when the parse had failed, in replace mode, which would have blanked
a year of commemorations in a calendar that had been working. It bounds the
day by the month and refuses to write when most of the year comes back empty.
today() shelled out to date(1) and fell back to UTC without a word when it was
absent, so a container reported the wrong day either side of midnight. It says
so now.
The UGCC loaded the Ruthenian menaion. That is the Metropolia of Pittsburgh's
calendar of saints, and lending it to Kyiv is the same error as lending the
law, in a release whose headline is that a church's law is never borrowed.
Also: has_obligation was dead API about to be frozen, and two ptr::eq
comparisons stood in for asking whether the discipline was overridden, which
is what they meant.
Three Eastern Catholic jurisdictions borrowed the Ruthenian ruleset, so a
Melkite, a Romanian Greek Catholic and a Ukrainian Greek Catholic were each
told that fourteen days a year bound them under the particular law of the
Metropolia of Pittsburgh. That is the exact error the level/obligation split
exists to prevent, committed by the library itself.
Every Eastern Catholic church legislates its own fasting discipline. Those
three are now shown at the Typikon with nothing reported as binding, which is
the honest position: the calendar is known, the law is not. Their entries say
so and a test holds it, asserting both that no jurisdiction but ruthenian
carries that ruleset and that the three claim no binding days.
The README gains a section on these churches, since supporting them was never
meant to be something a reader had to infer from the jurisdiction list.
For an Eastern Catholic church these are sharply different questions and the
library was collapsing them. Ruthenian particular law binds on four kinds of
day in the year; the tradition those parishes actually keep is the Byzantine
fast entire. Reporting only the law made a week of the Great Fast read as
'no meat, fish, eggs or dairy' seven times over, which is the legal floor and
not the fast.
--typikon shows the day at the Typikon's discipline while the obligation
still comes from the jurisdiction's own law, because that is the part only it
can say. The Ruthenian rule now answers 'what binds here' separately from
'how is this day kept', which is what makes the two composable at all.
Three tests hold the seam: exactly the same days bind whichever discipline is
displayed, the binding minimum is never stricter than the level shown, and
the flag is a no-op for a jurisdiction already on the Typikon.
The Carpatho-Rusyn diocese under Constantinople, formed from the Greek
Catholic parishes that returned to Orthodoxy in 1938. It was missing, which
is a strange gap in a calendar that already ships a fetcher for the Ruthenian
Metropolia's menaion: the two churches are the same people and the same
books, on either side of 1938.
The diocese publishes both an old-calendar and a new-calendar cycle and its
parishes keep one or the other, which acrod.org's own calendar toggle shows.
This entry is the old one; the note says how to get the other.
Both keep the same Typikon and read it a little differently, and until now
this library silently picked one reading and called it the Typikon. Two
differences are modelled, each one visible in the output and named in the
day's reason:
- The Protection of the Theotokos is a great feast in Slavic usage. Greek
practice ranks it lower, and the Church of Greece keeps it on 28 October
besides. This has teeth: rank is what lifts a Wednesday or Friday fast.
- Greek practice admits fish on any day of the Nativity Fast but Wednesday
and Friday until 17 December; Slavic practice keeps fish to Saturdays and
Sundays throughout.
Every jurisdiction carries the usage its books follow, and --tradition
overrides it for a parish that does not match its jurisdiction's default. The
difference in the calendar lives in a data overlay compiled in beside the
common menologion, which is where a local church's own commemorations would
go; the difference in the fast lives in the rule, because that is where it is.
Each rank also now carries the mark the books print beside it, circled cross,
cross and Jerusalem cross for great, vigil and polyeleos. Unicode has no glyph
for the polyeleos sign, a cross over a half-circle, so the Jerusalem cross
stands in as digital calendars generally settle on.
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.