Commit Graph
6 Commits
Author SHA1 Message Date
funkpower 227abac6f6 print it as a table, or without the glyphs
--format takes text, json, csv, tsv or ics. --json and --ics stay as
shorthand, because the widgets already call them.

csv and tsv are RFC 4180 quoted, one header row, one row per day, with
commemorations joined by a semicolon: a cell that is itself a table is worse
than a cell you have to split. --list-fields prints the twenty columns,
--fields picks a subset in the order you name them, and a column that does
not exist is caught before anything is printed rather than silently emitting
an empty cell.

--no-emoji drops the glyphs from every format, including the emoji field in
the JSON, and collapses the column in the text output rather than leaving a
hole where a glyph used to be.
2026-09-18 11:15:22 -04:00
funkpower d115616eff 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.
2026-09-18 11:13:15 -04:00
funkpower 8247b51805 point at menologion, and drop the em dashes from the prose
horologion was named for the Book of Hours, which is the fixed portions of
the services rather than the calendar of commemorations this displays. The
cross-links follow the rename.

Em dashes are gone from the prose. The two left in the README sit inside
console blocks quoting real output, where changing them would make the
README lie about what the program prints.
2026-09-18 10:53:03 -04:00
funkpower f2867f8212 the fast was reading the calendar and ignoring who was on it
An adversarial pass found the rules throwing away the feast rank that
describe() computes, in exactly the seasons where it decides the answer.

  - A saint of vigil or polyeleos rank now lifts a weekday of a fast, to wine
    and oil inside the Great Fast and as far as fish in the three lesser
    ones. This is why the printed calendars mark so many Wednesdays in the
    Nativity Fast; 74 days across 2020-2030 were being reported stricter than
    any published calendar keeps them, St Nicholas and the Forty Martyrs of
    Sebaste among them.
  - The Annunciation was tested after Great Friday and Great Saturday, so on
    the years it lands there it vanished into them entirely -- the Great Feast
    of the Incarnation demoted to a bullet, reported as a total fast, and its
    rank overwritten with the lesser movable one the rules then read. It is
    now tested first and lifts those days to wine and oil. Five such years
    this century on the Gregorian paschalion, seven on the Julian.
  - Lazarus Saturday and Palm Sunday belonged to no season at all, sitting
    between the Forty Days and Great Week. Any rule keying on the season fell
    through, so Byzantine Catholic jurisdictions reported a meat glyph and
    'no fast' on Palm Sunday.
  - The eves of the Nativity and Theophany are relaxed to wine and oil when
    they fall on a weekend, which ruthenian.py already implemented and this
    module did not.

Also: iCalendar folded on characters where RFC 5545 counts octets, so an
emoji put lines up to 80 octets over a 75-octet limit; a malformed --date
printed a traceback; the overlaid menologion was rebuilt for every single day
asked about; the Nativity of the Forerunner was ranked below its class.

Fourteen new tests, each one able to fail. The old sweep asserted only
all(d.fast), and 'none' is a truthy string.
2026-09-18 10:29:25 -04:00
funkpower e806b5b3ba elide the title on a word, and put the output in the README
The seven-day view cut each commemoration at a hard 44 characters, mid-word
and with nothing to say it had done so, which is how 'father of the
Forerunner' became 'father of the Fo'. It now cuts on a word boundary, says
so with an ellipsis, and takes its width from the terminal instead of a
constant, so a wide window shows the whole name.

Also adds a screenshot of the output, since a calendar is a thing people
want to look at before installing.
2026-09-18 09:52:31 -04:00
funkpower 11bbfddbe1 the Orthodox liturgical day, computed rather than fetched
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.
2026-09-18 09:09:32 -04:00