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.
This commit is contained in:
funkpower
2026-09-18 10:29:25 -04:00
parent e806b5b3ba
commit f2867f8212
11 changed files with 293 additions and 50 deletions
+1 -1
View File
@@ -24,7 +24,7 @@ $ typikon
## Install
```console
$ pip install typikon
$ pipx install git+https://git.pumpin.online/funkpower/typikon
```
Or run it straight out of a checkout — it has no dependencies and needs only
+1 -1
View File
@@ -37,7 +37,7 @@ typikon = "typikon.cli:main"
include = ["typikon*"]
[tool.setuptools.package-data]
typikon = ["data/*.json"]
typikon = ["data/*.json", "data/LICENSE"]
[tool.pytest.ini_options]
testpaths = ["tests"]
+140 -2
View File
@@ -7,10 +7,10 @@ import pytest
from typikon import describe, gregorian_pascha, julian_pascha, resolve
from typikon.calendars import from_julian, julian_offset, to_julian
from typikon.day import week
from typikon.day import week, year_of
from typikon.export import to_ics, to_json
from typikon.fasting import (DAIRY, FISH, NONE, OBLIGATORY, STRICT, WINE_OIL,
XEROPHAGY)
XEROPHAGY, stricter)
# ───────────────────────────── the paschalion ─────────────────────────────
@@ -228,3 +228,141 @@ def test_a_calendar_name_resolves_without_naming_a_church():
assert resolve("new").menologion == "revised_julian"
assert resolve("western").paschalion == "gregorian"
assert resolve(None).key == "new"
# ─────────────────── regressions, each from a real defect ──────────────────
def test_the_annunciation_is_never_lost_to_great_friday():
"""It can fall on Great Friday or Great Saturday, and when it does the
feast lifts the total fast rather than disappearing into it."""
for when, jurisdiction in ((dt.date(2016, 3, 25), "western"),
(dt.date(2005, 3, 25), "western"),
(dt.date(2029, 4, 7), "rocor"),
(dt.date(2018, 4, 7), "rocor")):
d = describe(when, jurisdiction)
assert d.church_date == "25 March", when
assert "Annunciation" in d.feast, when
assert d.rank == "great", when
assert d.fast == WINE_OIL, (when, d.fast)
def test_kyriopascha_and_the_annunciation_in_bright_week_stay_fast_free():
assert describe(dt.date(2035, 3, 25), "western").fast == NONE
assert describe(dt.date(2075, 4, 7), "rocor").fast == NONE
def test_palm_sunday_and_lazarus_saturday_are_still_the_fast():
"""Both fall between the Forty Days and Great Week. Leaving that gap
outside every season made the Byzantine Catholic rule report no fast at
all on Palm Sunday."""
for jurisdiction in ("ruthenian", "ugcc", "melkite", "goarch", "rocor"):
for year in (2025, 2026, 2027):
mv = year_of(year, jurisdiction)
for when in (mv["lazarus_saturday"], mv["palm_sunday"]):
d = describe(when, jurisdiction)
assert d.season is not None, (jurisdiction, when)
assert d.fast != NONE, (jurisdiction, when, d.fast)
assert d.reason, (jurisdiction, when)
def test_a_ranked_saint_relaxes_a_weekday_of_a_fasting_season():
"""The rank is why the printed calendars mark so many Wednesdays in the
Nativity Fast for wine and oil."""
# The Nativity of the Forerunner, a great feast inside the Apostles' Fast.
assert describe(dt.date(2020, 6, 24), "goarch").fast == FISH
# The Forty Martyrs of Sebaste, the textbook relaxation of a Lenten weekday.
assert describe(dt.date(2022, 3, 9), "goarch").fast == WINE_OIL
# St Nicholas, a vigil saint on a Wednesday of the Nativity Fast.
assert describe(dt.date(2023, 12, 6), "goarch").fast == WINE_OIL
def test_no_saint_brings_fish_into_the_great_fast_or_lifts_great_week():
for d in week(dt.date(2022, 3, 7), 40, "goarch"):
if d.season == "Great Fast" and d.church_date != "25 March":
assert not stricter(FISH, d.fast) or d.fast != FISH, d.date
if d.season == "Great and Holy Week" and d.church_date != "25 March":
assert d.fast in (STRICT, XEROPHAGY, WINE_OIL), (d.date, d.fast)
def test_the_eves_relax_when_they_fall_on_a_weekend():
"""The Liturgy of St Basil is served with Vespers, so the eve is kept at
wine and oil rather than xerophagy."""
assert describe(dt.date(2022, 12, 24), "goarch").fast == WINE_OIL
assert describe(dt.date(2023, 12, 24), "goarch").fast == WINE_OIL
assert describe(dt.date(2025, 1, 5), "goarch").fast == WINE_OIL
# A weekday eve is still xerophagy.
assert describe(dt.date(2024, 12, 24), "goarch").fast == XEROPHAGY
def test_the_apostles_fast_is_empty_when_pascha_is_late():
"""A late Pascha can leave the fast with no days at all, which is correct
rather than a bug -- and the old calendar never has that problem."""
goarch = [d for d in week(dt.date(2024, 5, 1), 120, "goarch")
if d.season == "Apostles' Fast"]
rocor = [d for d in week(dt.date(2024, 5, 1), 120, "rocor")
if d.season == "Apostles' Fast"]
assert goarch == []
assert len(rocor) > 0
# ──────────────────────── invariants, not samples ──────────────────────────
def test_the_paschalion_invariants_hold_for_two_centuries():
for year in range(1900, 2101):
j, g = julian_pascha(year), gregorian_pascha(year)
assert j.weekday() == 6, year
assert g.weekday() == 6, year
assert dt.date(year, 4, 4) <= j <= dt.date(year, 5, 8), (year, j)
assert dt.date(year, 3, 22) <= g <= dt.date(year, 4, 25), (year, g)
def test_every_civil_date_round_trips_through_the_julian_calendar():
day, last = dt.date(1800, 1, 1), dt.date(2200, 12, 31)
while day <= last:
y, m, d = to_julian(day)
assert from_julian(y, m, d) == day, day
day += dt.timedelta(1)
def test_the_julian_offset_widens_the_day_after_a_julian_leap_day():
for before, after in ((dt.date(1900, 3, 13), dt.date(1900, 3, 14)),
(dt.date(2100, 3, 14), dt.date(2100, 3, 15)),
(dt.date(2200, 3, 15), dt.date(2200, 3, 16))):
assert julian_offset(after) == julian_offset(before) + 1, after
def test_ics_content_lines_stay_within_seventy_five_octets():
for jurisdiction in ("goarch", "rocor", "ruthenian"):
text = to_ics(week(dt.date(2026, 1, 1), 365, jurisdiction))
for line in text.split("\r\n"):
assert len(line.encode()) <= 75, (jurisdiction, line)
def test_every_jurisdiction_holds_together_for_twenty_years():
"""The regression net. Replaces a loop that asserted only truthiness and
so could not fail: "none" is a truthy string."""
from typikon import JURISDICTIONS
from typikon.fasting import LEVELS
for key in JURISDICTIONS:
for d in week(dt.date(2020, 1, 1), 366 * 21 // 4, key):
assert d.fast in LEVELS, (key, d.date, d.fast)
assert d.binds in LEVELS, (key, d.date, d.binds)
assert d.commemorations, (key, d.date)
assert not stricter(d.binds, d.fast), (key, d.date)
assert d.title, (key, d.date)
# ──────────────────────────────── the CLI ──────────────────────────────────
def test_the_cli_rejects_a_malformed_date_instead_of_traversing_a_traceback():
from typikon.cli import main
assert main(["-d", "tomorrow"]) == 2
assert main(["-d", "2026-02-30"]) == 2
assert main(["--year", "99999"]) == 2
assert main(["--jurisdiction", "atlantis"]) == 2
def test_the_cli_elides_on_a_word():
from typikon.cli import _elide
assert _elide("The Radiant and Life-giving Resurrection", 24) == "The Radiant and…"
assert _elide("Short", 24) == "Short"
+6 -3
View File
@@ -4,7 +4,9 @@ Every date in this library exists twice. There is the **civil** date — what a
phone says, always Gregorian — and the **ecclesiastical** date, the day the
Church calls it. For a church on the new calendar those are the same date. For
one on the old calendar they differ by the drift between the Julian and
Gregorian reckonings: thirteen days from 1900 to 2099, fourteen from 2100.
Gregorian reckonings: thirteen days from 1900 until 14 March 2100, and
fourteen from the 15th -- the day after a Julian 29 February that the
Gregorian calendar does not have.
Keeping both is what makes a single menologion serve every jurisdiction. The
Nativity is 25 December in the Julian and the Gregorian books alike; the two
@@ -84,8 +86,9 @@ def from_julian(year, month, day):
def julian_offset(date):
"""How many days the Julian reckoning currently lags the Gregorian one.
Thirteen for our lifetimes; fourteen from 14 March 2100. Reported rather
than assumed so a caller can show it.
Thirteen for our lifetimes; fourteen from 15 March 2100, the day after a
Julian 29 February the Gregorian calendar does not have. Computed rather
than assumed, so nothing here breaks at the boundary.
"""
jy, jm, jd = to_julian(date)
return (gregorian_to_jdn(date.year, date.month, date.day)
+14 -2
View File
@@ -187,7 +187,11 @@ def main(argv=None):
print(f"typikon: unknown jurisdiction {name!r}; try --list", file=sys.stderr)
return 2
if args.year:
if args.year is not None:
if not 1 <= args.year <= 9999:
print(f"typikon: {args.year} is not a year this calendar reaches",
file=sys.stderr)
return 2
mv = year_of(args.year, jur)
print(f"\n {args.year} · {jur.name}\n")
for k in ("publican_pharisee", "meatfare", "cheesefare", "clean_monday",
@@ -200,7 +204,15 @@ def main(argv=None):
print()
return 0
start = dt.date.fromisoformat(args.date) if args.date else dt.date.today()
if args.date:
try:
start = dt.date.fromisoformat(args.date)
except ValueError:
print(f"typikon: {args.date!r} is not a date \u2014 expected YYYY-MM-DD",
file=sys.stderr)
return 2
else:
start = dt.date.today()
days = week(start, args.days, jur)
if args.json:
+6 -10
View File
@@ -31,7 +31,7 @@
"The Holy Martyrs Theopemptus and Theonas",
"Our Venerable Mother Syncletica"
],
"cycle": "the eve of the Theophany"
"cycle": "forefeast of the Theophany"
},
"01-06": {
"commemorations": [
@@ -956,7 +956,7 @@
"commemorations": [
"The Nativity of the Honourable and Glorious Prophet, Forerunner and Baptist John"
],
"rank": "vigil"
"rank": "great"
},
"06-25": {
"commemorations": [
@@ -976,8 +976,7 @@
"06-28": {
"commemorations": [
"The Translation of the Relics of the Holy Unmercenaries Cyrus and John"
],
"cycle": "the eve of Ss Peter and Paul"
]
},
"06-29": {
"commemorations": [
@@ -1160,15 +1159,13 @@
"07-31": {
"commemorations": [
"The Holy and Righteous Eudocimus"
],
"cycle": "the eve of the Procession of the Cross"
]
},
"08-01": {
"commemorations": [
"The Procession of the Precious and Life-giving Cross",
"The Seven Holy Maccabee Martyrs, their mother Solomone and their teacher Eleazar"
],
"cycle": "the Dormition Fast begins",
"rank": "polyeleos"
},
"08-02": {
@@ -1788,8 +1785,7 @@
"11-15": {
"commemorations": [
"The Holy Martyrs Gurias, Samonas and Habib"
],
"cycle": "the Nativity Fast begins"
]
},
"11-16": {
"commemorations": [
@@ -2014,7 +2010,7 @@
"commemorations": [
"The Holy Venerable Martyr Eugenia"
],
"cycle": "the eve of the Nativity"
"cycle": "forefeast of the Nativity"
},
"12-25": {
"commemorations": [
+21 -4
View File
@@ -98,6 +98,10 @@ class Day:
return out
# How the degrees of solemnity order against each other, for the days when a
# movable feast and a fixed one fall together.
_RANKS = {"great": 3, "vigil": 2, "polyeleos": 1}
_WEEKDAYS = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday",
"Saturday", "Sunday")
_MONTHS = ("January", "February", "March", "April", "May", "June", "July",
@@ -120,13 +124,26 @@ def describe(date, jurisdiction=None):
rank = entry.get("rank")
cycle = entry.get("cycle")
# A movable feast outranks whatever the fixed calendar holds that day.
# When a movable feast and a fixed one land together, the higher rank is
# the day's feast and the other joins the commemorations. Letting the
# movable one win unconditionally lost the Annunciation whenever it fell in
# Great Week -- the Great Feast of the Incarnation demoted to a bullet, and
# its rank overwritten with the lesser one the fasting rules then read.
fixed_feast = commemorations[0] if rank in ("great", "vigil") else None
mov_title, mov_rank = _menologion.movable_feast_for(date, mv)
if mov_title:
if mov_title and fixed_feast and _RANKS.get(rank, 0) > _RANKS.get(mov_rank, 0):
feast = fixed_feast
if mov_title not in commemorations:
commemorations.insert(1, mov_title)
elif mov_title:
feast = mov_title
rank = mov_rank or rank
if _RANKS.get(mov_rank, 0) >= _RANKS.get(rank, 0):
rank = mov_rank
if fixed_feast and fixed_feast not in commemorations:
commemorations.insert(1, fixed_feast)
else:
feast = commemorations[0] if rank in ("great", "vigil") else None
feast = fixed_feast
ctx = _Context(
civil=date, fixed=(fm, fd), fixed_year=fy, weekday=date.weekday(),
+18 -7
View File
@@ -10,7 +10,10 @@ import datetime as dt
__all__ = ["to_ics"]
_FOLD = 73
# RFC 5545: a content line SHOULD NOT be longer than 75 **octets**, excluding
# the line break. Counting characters instead put a line with four emoji eight
# octets over, and the em-dashes three each.
_FOLD = 75
_BACKSLASH = chr(92)
@@ -33,12 +36,20 @@ def _esc(text):
def _fold(line):
"""RFC 5545 wants lines under 75 octets, continued with a leading space."""
out = [line[:_FOLD]]
rest = line[_FOLD:]
while rest:
out.append(" " + rest[:_FOLD - 1])
rest = rest[_FOLD - 1:]
"""Break a content line on octets, continued with a leading space.
The split never falls inside a character: the budget is spent per
codepoint, so a four-octet emoji moves to the next line whole.
"""
out, cur, used = [], [], 0
for ch in line:
n = len(ch.encode("utf-8"))
if used + n > _FOLD:
out.append("".join(cur))
cur, used = [" "], 1
cur.append(ch)
used += n
out.append("".join(cur))
return "\r\n".join(out)
+13 -1
View File
@@ -74,8 +74,19 @@ def load(name="menologion"):
return m
_overlaid = {}
def for_jurisdiction(jur):
"""The common menologion with any supplementary sets overlaid."""
"""The common menologion with any supplementary sets overlaid.
Cached per jurisdiction: the overlay copies all 366 days, and rebuilding it
for every date asked about made a year of a jurisdiction with a
supplementary set ten times slower than one without.
"""
key = (jur.key, jur.menaia)
if key in _overlaid:
return _overlaid[key]
m = load()
for extra in jur.menaia:
try:
@@ -83,6 +94,7 @@ def for_jurisdiction(jur):
except FileNotFoundError:
# A supplementary set is optional; its absence is not an error.
pass
_overlaid[key] = m
return m
+62 -18
View File
@@ -16,16 +16,23 @@ reason. The known divergences:
given wine and oil. Several jurisdictions print them as strict.
- **Lazarus Saturday** is given wine and oil; caviar is the traditional
addition, which this vocabulary has no level for.
- **Great Week** is kept at xerophagy on its weekdays whatever saint falls in
it, because the days of the Passion outrank the menologion.
- **Monastic practice**, and the Holy Mountain in particular, is stricter
than any of this throughout.
A saint of vigil or polyeleos rank lifts the weekday discipline of a fast — to
wine and oil inside the Great Fast, as far as fish in the three lesser fasts.
That is why the printed calendars mark so many Wednesdays in the Nativity Fast
for wine and oil, and it is the rank in the menologion that decides it.
A calendar cannot settle those. Your bishop and your spiritual father can.
"""
import datetime as dt
from ..fasting import (Fast, STRICT, XEROPHAGY, WINE_OIL, FISH, DAIRY, NONE,
CUSTOMARY)
CUSTOMARY, laxest, stricter, strictest)
from ..seasons import (GREAT_FAST, GREAT_WEEK, BRIGHT_WEEK, CHEESEFARE_WEEK,
APOSTLES, DORMITION, NATIVITY_FAST, PENTECOST_WEEK,
CHRISTMASTIDE)
@@ -43,7 +50,7 @@ VOCABULARY = {
def _relaxed_by_feast(rank):
"""How far a feast lifts an ordinary Wednesday or Friday fast."""
"""How far a feast lifts a fast day."""
if rank == "great":
return FISH
if rank in ("vigil", "polyeleos"):
@@ -51,6 +58,23 @@ def _relaxed_by_feast(rank):
return None
def _for_feast(level, ctx, cap, reason):
"""Relax `level` for the day's saint, never past `cap`, never tightening.
This is the rule the printed calendars are applying when they mark a
Wednesday in the Nativity Fast for wine and oil: the day's rank lifts the
weekday discipline. Inside the Great Fast the lift stops at wine and oil —
no saint brings fish into Lent — and elsewhere it stops at fish.
"""
relaxed = _relaxed_by_feast(ctx.rank)
if relaxed is None:
return Fast(level, reason, CUSTOMARY)
lifted = strictest(laxest(level, relaxed), cap)
if lifted == level or stricter(lifted, level):
return Fast(level, reason, CUSTOMARY)
return Fast(lifted, f"{ctx.feast} \u2014 the fast is relaxed", CUSTOMARY)
def fast_for(ctx):
md, wd, mv = ctx.fixed, ctx.weekday, ctx.mv
season = ctx.season
@@ -71,6 +95,19 @@ def fast_for(ctx):
# ── The Great Fast and Great Week
if season in (GREAT_FAST, GREAT_WEEK):
# The Annunciation is tested before the strict days, not after. It can
# land on Great Friday or Great Saturday, and when it does the feast
# still lifts the total fast to wine and oil -- it does not vanish.
if md == (3, 25):
if ctx.civil in (mv["great_friday"], mv["holy_saturday"]):
return Fast(WINE_OIL,
"the Annunciation, on a day of the total fast",
CUSTOMARY)
return Fast(FISH, "the Annunciation", CUSTOMARY)
if ctx.civil == mv["palm_sunday"]:
return Fast(FISH, "Palm Sunday", CUSTOMARY)
if ctx.civil == mv["lazarus_saturday"]:
return Fast(WINE_OIL, "Lazarus Saturday", CUSTOMARY)
if ctx.civil == mv["clean_monday"]:
return Fast(STRICT, "the first day of the Great Fast", CUSTOMARY)
if ctx.civil == mv["great_friday"]:
@@ -78,18 +115,14 @@ def fast_for(ctx):
if ctx.civil == mv["holy_saturday"]:
# The one Saturday of the year kept without oil.
return Fast(XEROPHAGY, "Great and Holy Saturday", CUSTOMARY)
if md == (3, 25):
return Fast(FISH, "the Annunciation", CUSTOMARY)
if ctx.civil == mv["great_thursday"]:
return Fast(WINE_OIL, "Great and Holy Thursday", CUSTOMARY)
# Great Week keeps its own rank; a saint does not lift it.
if season == GREAT_WEEK:
return Fast(XEROPHAGY, f"a weekday of the {season}", CUSTOMARY)
if weekend:
return Fast(WINE_OIL, f"Saturday and Sunday of the {season}", CUSTOMARY)
return Fast(XEROPHAGY, f"a weekday of the {season}", CUSTOMARY)
if ctx.civil == mv["palm_sunday"]:
return Fast(FISH, "Palm Sunday", CUSTOMARY)
if ctx.civil == mv["lazarus_saturday"]:
return Fast(WINE_OIL, "Lazarus Saturday", CUSTOMARY)
return _for_feast(XEROPHAGY, ctx, WINE_OIL, f"a weekday of the {season}")
# ── Cheesefare: meat is set aside for good, everything else stays,
# Wednesday and Friday included.
@@ -101,28 +134,36 @@ def fast_for(ctx):
# ── The Nativity Fast, which tightens for its last five days
if season == NATIVITY_FAST:
if md == (12, 24):
# The eve is relaxed to wine and oil on a Saturday or Sunday,
# because the Liturgy of St Basil is served with Vespers.
if weekend:
return Fast(WINE_OIL, "the eve of the Nativity, on a weekend",
CUSTOMARY)
return Fast(XEROPHAGY, "the eve of the Nativity", CUSTOMARY)
if md == (11, 21):
return Fast(FISH, "the Entrance of the Theotokos", CUSTOMARY)
if md >= (12, 20):
if wd in (TUE, THU) or weekend:
return Fast(WINE_OIL, "the forefeast of the Nativity", CUSTOMARY)
return Fast(XEROPHAGY, "the forefeast of the Nativity", CUSTOMARY)
return _for_feast(XEROPHAGY, ctx, WINE_OIL,
"the forefeast of the Nativity")
if weekend:
return Fast(FISH, "Saturday and Sunday of the Nativity Fast", CUSTOMARY)
if wd in (TUE, THU):
return Fast(WINE_OIL, "Tuesday and Thursday of the Nativity Fast", CUSTOMARY)
return Fast(XEROPHAGY, "Monday, Wednesday and Friday of the Nativity Fast",
CUSTOMARY)
return _for_feast(WINE_OIL, ctx, FISH,
"Tuesday and Thursday of the Nativity Fast")
return _for_feast(XEROPHAGY, ctx, FISH,
"Monday, Wednesday and Friday of the Nativity Fast")
# ── The Apostles' Fast, kept the same way
if season == APOSTLES:
if weekend:
return Fast(FISH, "Saturday and Sunday of the Apostles' Fast", CUSTOMARY)
if wd in (TUE, THU):
return Fast(WINE_OIL, "Tuesday and Thursday of the Apostles' Fast", CUSTOMARY)
return Fast(XEROPHAGY, "Monday, Wednesday and Friday of the Apostles' Fast",
CUSTOMARY)
return _for_feast(WINE_OIL, ctx, FISH,
"Tuesday and Thursday of the Apostles' Fast")
return _for_feast(XEROPHAGY, ctx, FISH,
"Monday, Wednesday and Friday of the Apostles' Fast")
# ── The Dormition Fast, kept like the Great Fast
if season == DORMITION:
@@ -130,10 +171,13 @@ def fast_for(ctx):
return Fast(FISH, "the Transfiguration", CUSTOMARY)
if weekend:
return Fast(WINE_OIL, "Saturday and Sunday of the Dormition Fast", CUSTOMARY)
return Fast(XEROPHAGY, "a weekday of the Dormition Fast", CUSTOMARY)
return _for_feast(XEROPHAGY, ctx, WINE_OIL,
"a weekday of the Dormition Fast")
# ── The three fixed fast days outside any season
if md == (1, 5):
if weekend:
return Fast(WINE_OIL, "the eve of Theophany, on a weekend", CUSTOMARY)
return Fast(XEROPHAGY, "the eve of Theophany", CUSTOMARY)
if md == (9, 14):
return Fast(WINE_OIL, "the Exaltation of the Holy Cross", CUSTOMARY)
+11 -1
View File
@@ -1,5 +1,11 @@
"""Which season of the year a day falls in.
The Great Fast here runs from Clean Monday through Palm Sunday. The Forty Days
proper end on the Friday before Lazarus Saturday — ``great_fast_end`` in the
movable cycle, which is what the fasting rules count from — but a season is a
label for a reader, and a Palm Sunday belonging to no season at all is worse
than one belonging loosely to the Fast.
The movable seasons are counted from Pascha and so are compared as civil
dates. The fixed ones — the Nativity and Dormition Fasts, the fast-free days
after the Nativity — are compared in **ecclesiastical** space, against the date
@@ -41,7 +47,11 @@ def season_for(civil, fixed_md, mv, apostles_end):
return BRIGHT_WEEK
if mv["great_week_start"] <= civil <= mv["holy_saturday"]:
return GREAT_WEEK
if mv["clean_monday"] <= civil <= mv["great_fast_end"]:
# Lazarus Saturday and Palm Sunday sit between the Forty Days, which end on
# the Friday before them, and Great Week, which begins after them. Leaving
# that gap unnamed left both days with no season at all, and any rule that
# keyed on the season fell through to "no fast" on Palm Sunday.
if mv["clean_monday"] <= civil <= mv["palm_sunday"]:
return GREAT_FAST
if mv["cheesefare_week_start"] <= civil <= mv["cheesefare"]:
return CHEESEFARE_WEEK