It is simple but tedious to calculate the date of Easter in the
gregorian calendar. The calculation below comes from the book
*Astronomical Algorithms* by Jean Meeus via
en.wikipedia.org/wiki/computus.

(define (easter year) (let* ((a (modulo year 19)) (b (quotient year 100)) (c (modulo year 100)) (d (quotient b 4)) (e (modulo b 4)) (f (quotient (+ b 8) 25)) (g (quotient (+ (- b f) 1) 3)) (h (modulo (- (+ (* 19 a) b 15) d g) 30)) (i (quotient c 4)) (k (modulo c 4)) (l (modulo (- (+ 32 (* 2 e) (* 2 i)) h k) 7)) (m (quotient (+ a (* 11 h) (* 22 l)) 451)) (month (quotient (- (+ h l 114) (* 7 m)) 31)) (day (+ (modulo (- (+ h l 114) (* 7 m)) 31) 1))) (values month day)))

Here is an example showing the use of easter:

| (let loop ((year 2000)) | (call-with-values | (lambda () (easter year)) | (lambda (month day) | (for-each display | `(,year ": " ,(if (= month 3) "March" "April") " " ,day)) | (newline))) | (if (< year 2020) (loop (+ year 1)))) | | 2000: April 23 | 2001: April 15 | 2002: March 31 | 2003: April 20 | 2004: April 11 | 2005: March 27 | 2006: April 16 | 2007: April 8 | 2008: March 23 | 2009: April 12 | 2010: April 4 | 2011: April 24 | 2012: April 8 | 2013: March 31 | 2014: April 20 | 2015: April 5 | 2016: March 27 | 2017: April 16 | 2018: April 1 | 2019: April 21 | 2020: April 12

Various other dates are based on Easter. All can be calculated using the easter-add and easter-sub functions:

(define (easter-add year days) (call-with-values (lambda () (easter year)) (lambda (m d) (let loop ((m m) (d (+ d days))) (cond ((and (= m 3) (> d 31)) (loop (+ m 1) (- d 31))) ((and (= m 4) (> d 30)) (loop (+ m 1) (- d 30))) ((and (= m 5) (> d 31)) (loop (+ m 1) (- d 31))) ((and (= m 6) (> d 30)) (loop (+ m 1) (- d 30))) (else (values m d)))))))

(define (easter-sub year days) (call-with-values (lambda () (easter year)) (lambda (m d) (let ((feb (if (zero? (modulo year 4)) 29 28))) (let loop ((m m) (d (- d days))) (cond ((and (= m 4) (< d 1)) (loop (- m 1) (+ d 31))) ((and (= m 3) (< d 1)) (loop (- m 1) (+ d feb))) ((and (= m 2) (< d 1)) (loop (- m 1) (+ d 31))) (else (values m d))))))))

Easter-sub uses an invalid test for leap year, but it will work until after I die, so I don't care.

(define (mardi-gras year) (easter-sub year 47)) (define (ash-wednesday year) (easter-sub year 46)) (define (palm-sunday year) (easter-sub year 7)) (define (holy-thursday year) (easter-sub year 3)) (define (good-friday year) (easter-sub year 2)) (define (holy-saturday year) (easter-sub year 1)) (define (easter-vigil year) (easter-sub year 1)) (define (divine-mercy year) (easter-add year 7)) (define (ascension year) (easter-add year 39)) (define (pentecost year) (easter-add year 49)) (define (corpus-christi year) (easter-add year 63))