aboutsummaryrefslogtreecommitdiff
path: root/internal/calendar/calendar.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/calendar/calendar.go')
-rw-r--r--internal/calendar/calendar.go79
1 files changed, 38 insertions, 41 deletions
diff --git a/internal/calendar/calendar.go b/internal/calendar/calendar.go
index 00048b7..a033c81 100644
--- a/internal/calendar/calendar.go
+++ b/internal/calendar/calendar.go
@@ -36,45 +36,22 @@ func computeEF(date time.Time, sel Selection, layers []Layer) LiturgicalDay {
td := temporalEF(date)
merged := mergeLayers(layers)
year := date.Year()
- // occupiedByRank reports whether some OTHER fixed-date sanctoral
- // celebration whose rank passes `allowed` resolves onto d this year.
- // transferIfImpededEF uses this at two different thresholds: class 1
- // only, to decide whether a candidate is impeded in the first place (a
- // class-2 occupant never impedes a class-1 feast -- class 1 always beats
- // class 2 outright, no tie exists); and class 1 OR 2, for RG 96's "next
- // day that is not I or II class" once a transfer is already under way
- // (e.g. the Visitation, 2 July, blocking the Precious Blood's transfer
- // off 1 July in 2011).
- occupiedByRank := func(d time.Time, exceptSlug string, allowed func(Rank) bool) bool {
- for slug2, rc2 := range merged {
- if slug2 == exceptSlug {
- continue
- }
- cel2 := buildCelebration(slug2, rc2)
- if !allowed(cel2.Rank) {
- continue
- }
- if when2, ok := celebrationDate(cel2, year, sel); ok && sameDay(when2, d) {
- return true
- }
- }
- return false
- }
- isClass1 := func(r Rank) bool { return r == RankClass1 }
- isClass1Or2 := func(r Rank) bool { return r == RankClass1 || r == RankClass2 }
- occ1 := func(d time.Time, except string) bool { return occupiedByRank(d, except, isClass1) }
- occ1Or2 := func(d time.Time, except string) bool { return occupiedByRank(d, except, isClass1Or2) }
// RG 97/98: the year's impeded I-class transfers are resolved as a set,
// not one at a time, so two feasts impeded by the same early Easter cannot
// both claim the same free day and lose one of themselves.
//
- // efTransferPlan is pure in (year, merged content, sel) and identical for
- // every day of the year it is asked about -- computeEF runs once PER DAY,
- // so a multi-day view (mobile.Days's week, a month view) was rebuilding
- // it from scratch on every single one. efTransferPlanCached memoises it;
- // see transfer_plan_cache.go for the cache key and why each of its three
- // parts is load-bearing.
- plan := efTransferPlanCached(year, merged, sel, occ1, occ1Or2)
+ // Both the plan and the occupancy index it is built from are pure in
+ // (year, merged content, sel) and identical for every day of the year
+ // they are asked about -- computeEF runs once PER DAY, so a multi-day
+ // view (mobile.Days's week, a month view) was rebuilding both from
+ // scratch on every single one. efTransferPlanCached memoises them
+ // together; see transfer_plan_cache.go for the cache key, why each of
+ // its three parts is load-bearing, and what the occupancy index is an
+ // index OF (every merged entry's own ORIGINAL, untransferred date --
+ // never a transfer TARGET, which is decided during planning and tracked
+ // separately, only within one planning pass, by efTransferPlan's own
+ // `claimed`).
+ plan, occ := efTransferPlanCached(year, merged, sel)
cands := []candidate{{Cel: td.Cel, Temporal: true, Season: td.Season, Sunday: td.Sunday}}
for slug, rc := range merged {
cel := buildCelebration(slug, rc)
@@ -90,9 +67,22 @@ func computeEF(date time.Time, sel Selection, layers []Layer) LiturgicalDay {
}
effective, planned := plan[cel.Slug]
if !planned {
+ // occ.occupied answers exactly what computeEF's own former
+ // occupiedByRank closure did -- "does some OTHER fixed-date
+ // sanctoral celebration whose rank passes `allowed` resolve onto
+ // d this year" -- at the same two thresholds transferIfImpededEF
+ // has always used: class 1 only, to decide whether a candidate is
+ // impeded in the first place (a class-2 occupant never impedes a
+ // class-1 feast -- class 1 always beats class 2 outright, no
+ // tie-break is even reached); and class 1 OR 2, for RG 96's "next
+ // day that is not I or II class" once a transfer is already under
+ // way (e.g. the Visitation, 2 July, blocking the Precious
+ // Blood's transfer off 1 July in 2011). It now reads a
+ // precomputed index instead of scanning merged afresh -- see
+ // transfer_plan_cache.go.
effective = transferIfImpededEF(cel, when,
- func(d time.Time) bool { return occ1(d, cel.Slug) },
- func(d time.Time) bool { return occ1Or2(d, cel.Slug) })
+ func(d time.Time) bool { return occ.occupied(d, cel.Slug, isClass1Rank) },
+ func(d time.Time) bool { return occ.occupied(d, cel.Slug, isClass1Or2Rank) })
}
if sameDay(effective, date) {
cands = append(cands, candidate{Cel: cel, Temporal: false, Season: td.Season})
@@ -248,6 +238,14 @@ func transferIfImpeded(cel Celebration, when time.Time, sel Selection) time.Time
return day
}
+// isClass1Rank and isClass1Or2Rank are the two occupancy thresholds
+// efTransferPlan and transferIfImpededEF (via computeEF's call site) test
+// against efOccupancyIndex -- see transfer_plan_cache.go's doc comment on
+// efOccupancyIndex for what "occupied" means and why it is safe to
+// precompute once per (year, merged content, sel).
+func isClass1Rank(r Rank) bool { return r == RankClass1 }
+func isClass1Or2Rank(r Rank) bool { return r == RankClass1 || r == RankClass2 }
+
// efTransferPlan resolves ALL of a year's impeded I-class transfers together,
// which RG 97/98 require and which resolving them one at a time cannot do.
//
@@ -268,8 +266,7 @@ func transferIfImpeded(cel Celebration, when time.Time, sel Selection) time.Time
// takes its proper seat on 2 April; St Joseph, impeded on the 19th, walks past
// Holy Week, the Easter octave and that claimed Monday to 3 April. Before this,
// St Joseph was observed on no day of 2008, 2035 or 2046 at all.
-func efTransferPlan(year int, merged map[string]RawCelebration, sel Selection,
- occupiedByClass1, occupiedByClass1Or2 func(time.Time, string) bool) map[string]time.Time {
+func efTransferPlan(year int, merged map[string]RawCelebration, sel Selection, occ efOccupancyIndex) map[string]time.Time {
type pending struct {
slug string
@@ -297,7 +294,7 @@ func efTransferPlan(year int, merged map[string]RawCelebration, sel Selection,
st := temporalEF(when)
tCand := candidate{Cel: st.Cel, Temporal: true, Season: st.Season, Sunday: st.Sunday}
sCand := candidate{Cel: cel, Temporal: false}
- if precedenceEF(tCand) >= precedenceEF(sCand) && !occupiedByClass1(when, cel.Slug) {
+ if precedenceEF(tCand) >= precedenceEF(sCand) && !occ.occupied(when, cel.Slug, isClass1Rank) {
continue // not impeded; stays put
}
// RG 96(a): a proper seat, claimed before anything queues. Guarded to
@@ -328,7 +325,7 @@ func efTransferPlan(year int, merged map[string]RawCelebration, sel Selection,
for i := 0; i < 60; i++ {
b := temporalEF(day)
isHighClass := b.Cel.Rank == RankClass1 || b.Cel.Rank == RankClass2
- if isHighClass || occupiedByClass1Or2(day, p.cel.Slug) || claimed[day.Format("2006-01-02")] {
+ if isHighClass || occ.occupied(day, p.cel.Slug, isClass1Or2Rank) || claimed[day.Format("2006-01-02")] {
day = day.AddDate(0, 0, 1)
continue
}