    {-# LANGUAGE Safe #-}

    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 :: Year -> Day
    orthodoxPaschalMoon :: Integer -> Day
    orthodoxPaschalMoon Integer
    year = Integer -> Day -> Day
    addDays (-Integer
    shiftedEpact) (Integer -> MonthOfYear -> MonthOfYear -> Day
    fromJulian Integer
    jyear MonthOfYear
    4 MonthOfYear
    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 :: Year -> 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 :: Year -> Day
    gregorianPaschalMoon :: Integer -> Day
    gregorianPaschalMoon Integer
    year = Integer -> Day -> Day
    addDays (-Integer
    adjustedEpact) (Integer -> MonthOfYear -> MonthOfYear -> Day
    fromGregorian Integer
    year MonthOfYear
    4 MonthOfYear
    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 :: Year -> 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