module Data.Time.Calendar.Easter
    (
    sundayAfter,
    orthodoxPaschalMoon,orthodoxEaster,
    gregorianPaschalMoon,gregorianEaster
    ) where

-- formulae from Reingold & Dershowitz, _Calendrical Calculations_, ch. 8.

import Data.Time.Calendar
import Data.Time.Calendar.Julian

-- | The next Sunday strictly after a given day.
sundayAfter :: Day -> Day
sundayAfter :: Day -> Day
sundayAfter Day
day = Integer -> Day -> Day
addDays (Integer
7 Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
- (Integer -> Integer -> Integer
forall a. Integral a => a -> a -> a
mod (Day -> Integer
toModifiedJulianDay Day
day Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ Integer
3) Integer
7)) Day
day

-- | Given a year, find the Paschal full moon according to Orthodox Christian tradition
orthodoxPaschalMoon :: Integer -> Day
orthodoxPaschalMoon :: Integer -> Day
orthodoxPaschalMoon Integer
year  = Integer -> Day -> Day
addDays (- Integer
shiftedEpact) (Integer -> Int -> Int -> Day
fromJulian Integer
jyear Int
4 Int
19) where
    shiftedEpact :: Integer
shiftedEpact = Integer -> Integer -> Integer
forall a. Integral a => a -> a -> a
mod (Integer
14 Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ Integer
11 Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
* (Integer -> Integer -> Integer
forall a. Integral a => a -> a -> a
mod Integer
year Integer
19)) Integer
30
    jyear :: Integer
jyear = if Integer
year Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
> Integer
0 then Integer
year else Integer
year Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
- Integer
1

-- | Given a year, find Easter according to Orthodox Christian tradition
orthodoxEaster :: Integer -> Day
orthodoxEaster :: Integer -> Day
orthodoxEaster = Day -> Day
sundayAfter (Day -> Day) -> (Integer -> Day) -> Integer -> Day
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Integer -> Day
orthodoxPaschalMoon

-- | Given a year, find the Paschal full moon according to the Gregorian method
gregorianPaschalMoon :: Integer -> Day
gregorianPaschalMoon :: Integer -> Day
gregorianPaschalMoon Integer
year  = Integer -> Day -> Day
addDays (- Integer
adjustedEpact) (Integer -> Int -> Int -> Day
fromGregorian Integer
year Int
4 Int
19) where
    century :: Integer
century = (Integer -> Integer -> Integer
forall a. Integral a => a -> a -> a
div Integer
year Integer
100) Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ Integer
1
    shiftedEpact :: Integer
shiftedEpact = Integer -> Integer -> Integer
forall a. Integral a => a -> a -> a
mod (Integer
14 Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ Integer
11 Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
* (Integer -> Integer -> Integer
forall a. Integral a => a -> a -> a
mod Integer
year Integer
19) Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
- (Integer -> Integer -> Integer
forall a. Integral a => a -> a -> a
div (Integer
3 Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
* Integer
century) Integer
4) Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ (Integer -> Integer -> Integer
forall a. Integral a => a -> a -> a
div (Integer
5 Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ Integer
8 Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
* Integer
century) Integer
25)) Integer
30
    adjustedEpact :: Integer
adjustedEpact = if Integer
shiftedEpact Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
== Integer
0 Bool -> Bool -> Bool
|| ((Integer
shiftedEpact Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
== Integer
1) Bool -> Bool -> Bool
&& (Integer -> Integer -> Integer
forall a. Integral a => a -> a -> a
mod Integer
year Integer
19 Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
< Integer
10)) then Integer
shiftedEpact Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ Integer
1 else Integer
shiftedEpact

-- | Given a year, find Easter according to the Gregorian method
gregorianEaster :: Integer -> Day
gregorianEaster :: Integer -> Day
gregorianEaster = Day -> Day
sundayAfter (Day -> Day) -> (Integer -> Day) -> Integer -> Day
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Integer -> Day
gregorianPaschalMoon