{-|
Module: Data.Astro.Time.GregorianCalendar
Description: Gregorian Calendar
Copyright: Alexander Ignatyev, 2016


Gregorian Calendar was introduced by Pope Gregory XIII.
He abolished the days 1582-10-05 to 1582-10-14 inclusive to bring back civil and tropical years back to line.
-}

module Data.Astro.Time.GregorianCalendar
(
  isLeapYear
  , dayNumber
  , easterDayInYear
  , gregorianDateAdjustment
)

where

import Data.Time.Calendar (Day(..), fromGregorian, toGregorian)

-- Date after 15 October 1582 belongs to Gregorian Calendar
-- Before this date - to Julian Calendar
isGregorianDate :: Integer -> Int -> Int -> Bool
isGregorianDate y m d = y > gyear
  || (y == gyear && m > gmonth)
  || (y == gyear && m == gmonth && d >= gday)
  where gyear = 1582
        gmonth = 10
        gday = 15


gregorianDateAdjustment :: Integer -> Int ->Int -> Int
gregorianDateAdjustment year month day =
  if isGregorianDate year month day
  then let y = if month < 3 then year - 1 else year
           y' = fromIntegral y
           a = truncate (y' / 100)
       in 2 - a + truncate(fromIntegral a/4)
  else 0


-- | Check Gregorian calendar leap year
isLeapYear :: Integer -> Bool
isLeapYear year =
  year `mod` 4 == 0
  && (year `mod` 100 /= 0 || year `mod` 400 == 0)


-- | Day Number in a year
dayNumber :: Day -> Int
dayNumber date =
  (daysBeforeMonth year month) + day
  where (year, month, day) = toGregorian date


-- | Get Easter date
-- function uses absolutely crazy Butcher's algorithm
easterDayInYear :: Int -> Day
easterDayInYear year =
  let  a = year `mod` 19
       b = year `div` 100
       c = year `mod` 100
       d = b `div` 4
       e = b `mod` 4
       f = (b+8) `div` 25
       g = (b-f+1) `div` 3
       h = (19*a+b-d-g+15) `mod` 30
       i = c `div` 4
       k = c `mod` 4
       l = (32+2*e+2*i-h-k) `mod` 7
       m = (a+11*h+22*l) `div` 451
       n' = (h+l-7*m+114)
       n = n' `div` 31
       p = n' `mod` 31
  in fromGregorian (fromIntegral year) n (p+1)


daysBeforeMonth :: Integer -> Int -> Int
daysBeforeMonth year month =
  let a = if isLeapYear year then 62 else 63
      month' = (fromIntegral month) :: Double
  in if month > 2 then
    truncate $ ((month' + 1.0) * 30.6) - a
  else truncate $ (month' - 1.0)*a*0.5
