calendar: compute real Easter dates and temporal-cycle seasons
resolveDay() no longer hardcodes season: 'trinitytide'. Easter is computed via the Anonymous Gregorian algorithm (verified against 16 known reference dates spanning 1583-2100); Advent/Christmastide/ Epiphanytide follow the Christmas-anchored rules (including Advent's "Sunday nearest Nov 30"); Septuagesima through Trinitytide come from easter-offsets.yml, reshaped from single named anchor points into real ranges plus single-day overrides for Corpus Christi and Sacred Heart. occurring feasts (calendar/feasts.ts) are still a stub — the sanctoral calendar and Double-vs-not ranking are a separate, larger project.
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { easterSunday } from '../../src/calendar/easter';
|
||||
import { toIsoDate } from '../../src/calendar/date-math';
|
||||
|
||||
// Reference dates are public record (Western/Gregorian Easter Sunday),
|
||||
// spanning different centuries and both early and late extremes.
|
||||
const KNOWN_EASTER_DATES: Record<number, string> = {
|
||||
1583: '1583-04-10', // first year the Gregorian algorithm is valid
|
||||
1900: '1900-04-15',
|
||||
1954: '1954-04-18',
|
||||
2000: '2000-04-23',
|
||||
2019: '2019-04-21',
|
||||
2020: '2020-04-12',
|
||||
2021: '2021-04-04',
|
||||
2022: '2022-04-17',
|
||||
2023: '2023-04-09',
|
||||
2024: '2024-03-31', // one of the earliest possible dates
|
||||
2025: '2025-04-20',
|
||||
2026: '2026-04-05',
|
||||
2027: '2027-03-28',
|
||||
2028: '2028-04-16',
|
||||
2038: '2038-04-25', // one of the latest possible dates
|
||||
2100: '2100-03-28',
|
||||
};
|
||||
|
||||
describe('easterSunday', () => {
|
||||
for (const [year, expected] of Object.entries(KNOWN_EASTER_DATES)) {
|
||||
it(`resolves ${year} to ${expected}`, () => {
|
||||
expect(toIsoDate(easterSunday(Number(year)))).toBe(expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { resolveSeason } from '../../src/calendar/temporal';
|
||||
|
||||
// Easter 2026 = Apr 5. Reference boundaries computed by hand from that.
|
||||
describe('resolveSeason', () => {
|
||||
it('resolves Advent (the Sunday nearest Nov 30) through Dec 24', () => {
|
||||
// Nov 30, 2025 is a Sunday -> Advent I starts that day.
|
||||
expect(resolveSeason('2025-11-29')).toBe('trinitytide');
|
||||
expect(resolveSeason('2025-11-30')).toBe('advent');
|
||||
expect(resolveSeason('2025-12-24')).toBe('advent');
|
||||
});
|
||||
|
||||
it('resolves Christmastide across the year boundary (Dec 25 - Jan 5)', () => {
|
||||
expect(resolveSeason('2025-12-25')).toBe('christmastide');
|
||||
expect(resolveSeason('2025-12-31')).toBe('christmastide');
|
||||
expect(resolveSeason('2026-01-01')).toBe('christmastide');
|
||||
expect(resolveSeason('2026-01-05')).toBe('christmastide');
|
||||
});
|
||||
|
||||
it('resolves Epiphanytide from Jan 6 until Septuagesima', () => {
|
||||
expect(resolveSeason('2026-01-06')).toBe('epiphanytide');
|
||||
// Easter 2026 is Apr 5, so Septuagesima (-63 days) is Feb 1.
|
||||
expect(resolveSeason('2026-01-31')).toBe('epiphanytide');
|
||||
});
|
||||
|
||||
it('resolves the Easter-offset seasons for 2026 (Easter = Apr 5)', () => {
|
||||
expect(resolveSeason('2026-02-01')).toBe('septuagesima'); // Easter - 63
|
||||
expect(resolveSeason('2026-02-18')).toBe('lent'); // Ash Wednesday, Easter - 46
|
||||
expect(resolveSeason('2026-03-22')).toBe('passiontide'); // Easter - 14
|
||||
expect(resolveSeason('2026-04-05')).toBe('eastertide'); // Easter Sunday
|
||||
expect(resolveSeason('2026-05-14')).toBe('ascensiontide'); // Easter + 39
|
||||
expect(resolveSeason('2026-05-24')).toBe('pentecost'); // Easter + 49
|
||||
expect(resolveSeason('2026-05-31')).toBe('trinitytide'); // Easter + 56
|
||||
});
|
||||
|
||||
it('resolves the Corpus Christi and Sacred Heart single-day overrides', () => {
|
||||
expect(resolveSeason('2026-06-04')).toBe('corpus-christi'); // Easter + 60
|
||||
expect(resolveSeason('2026-06-12')).toBe('sacred-heart'); // Easter + 68
|
||||
// The days immediately around them are still plain Trinitytide.
|
||||
expect(resolveSeason('2026-06-05')).toBe('trinitytide');
|
||||
});
|
||||
|
||||
it('resolves plain Trinitytide between Trinity Sunday and Advent', () => {
|
||||
expect(resolveSeason('2026-09-15')).toBe('trinitytide');
|
||||
});
|
||||
|
||||
it('never needs a cross-year Easter lookup for January dates', () => {
|
||||
// A year with a very early Easter (2027-03-28) still keeps Septuagesima
|
||||
// (Jan 24) safely after Epiphany (Jan 6).
|
||||
expect(resolveSeason('2027-01-06')).toBe('epiphanytide');
|
||||
expect(resolveSeason('2027-01-23')).toBe('epiphanytide');
|
||||
expect(resolveSeason('2027-01-24')).toBe('septuagesima');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user